diff --git a/articles/building-apps/views/add-master-detail.adoc b/articles/building-apps/views/add-master-detail.adoc index ea7e52bea7..421201e2fc 100644 --- a/articles/building-apps/views/add-master-detail.adoc +++ b/articles/building-apps/views/add-master-detail.adoc @@ -111,9 +111,9 @@ The [methodname]`addBackdropClickListener()` and [methodname]`addDetailEscapePre The signal keeps the selection in memory, which is enough for many views. It isn't preserved across a page reload, though, and it can't be shared as a link. If you need the selected item to be bookmarkable or to survive a refresh, represent it in the URL. -To do this, keep the selection signal as the source of truth and synchronize it with a <> or <>: update the URL when the signal changes, and initialize the signal from the URL on navigation. The detail area still follows the signal reactively; only the source that drives the signal changes. +To do this, keep the selection signal as the source of truth and synchronize it with a <> or <>: initialize the signal from the URL when the view is entered, and navigate to a new URL when the signal changes. The detail area still follows the signal reactively; only the source that drives the signal changes. [NOTE] -Reading and writing query and route parameters directly as signals is planned for a future release, which will make synchronizing the selection with the URL even more straightforward. +The current route and query parameters are available as a read-only signal through [methodname]`UI.getCurrentOrThrow().routerStateSignal()`, so the URL can drive the selection reactively; you write changes back by navigating. The <>, <>, and <> guides show the patterns for each. With the master, the selection signal, and the reactive detail in place, the master-detail view is complete. The next step is to integrate it into your application and enhance it with real data and more complex UI components as needed. diff --git a/articles/building-apps/views/pass-data/query-parameters.adoc b/articles/building-apps/views/pass-data/query-parameters.adoc index 3836d54ed0..b0300a8c8a 100644 --- a/articles/building-apps/views/pass-data/query-parameters.adoc +++ b/articles/building-apps/views/pass-data/query-parameters.adoc @@ -10,7 +10,7 @@ order: 30 = Query Parameters :toclevels: 2 -In this guide, you'll learn how to access and set query parameters in a Vaadin view. +In this guide, you'll learn how to read and set query parameters in a Vaadin view, and how to bind a field to a query parameter with a <> so the two stay in sync. == Copy-Paste into Your Project @@ -22,7 +22,7 @@ If you want to quickly try out query parameters in your Vaadin application, copy include::{root}/src/main/java/com/vaadin/demo/buildingapps/passdata/QueryParameterView.java[tags=snippet,indent=0] ---- -For more detailed instructions on how to use route parameters, continue reading below. +This view reads the `filter` query parameter from the router state signal and two-way binds it to a text field: the field shows the current value, and editing it updates the URL. For more detailed instructions on how to use query parameters, continue reading below. == What Are Query Parameters? @@ -42,45 +42,28 @@ Query parameters are commonly used to store view-related state, such as a grid's If you need required parameters, consider using <> instead. -== Accessing Query Parameter Values +== Reading Query Parameters Reactively -You access query parameters similarly to <>. Your view must implement the [interfacename]`BeforeEnterObserver` interface, which defines the [methodname]`beforeEnter()` method. Inside this method, you can retrieve query parameters: +Query parameters are part of the current location, which is available as a <>. Calling [methodname]`UI.getCurrentOrThrow().routerStateSignal()` returns a read-only signal whose value is a [classname]`RouterState` record; its [methodname]`location()` method gives you the [classname]`Location`, and [methodname]`getQueryParameters()` the [classname]`QueryParameters` for the active navigation. + +As with <>, a convenient pattern is to derive a separate signal for each parameter. Because [interfacename]`Signal` is a functional interface, you can define one with a lambda that reads the value from the location: [source,java] ---- -@Route -public class OrdersView extends Main implements BeforeEnterObserver { +private static final String QUERY_PARAM_FILTER = "filter"; // <1> - private static final String QUERY_PARAM_SORT = "sort"; // <1> - private static final String QUERY_PARAM_FILTER = "filter"; - - @Override - public void beforeEnter(BeforeEnterEvent event) { - event.getLocation().getQueryParameters().getSingleParameter(QUERY_PARAM_SORT) - .ifPresent(sort -> { - // Process the sort parameter - }); - event.getLocation().getQueryParameters().getSingleParameter(QUERY_PARAM_FILTER) - .ifPresent(filter -> { - // Process the filter parameter - }); - } - ... -} +private final Signal filterParam = () -> UI.getCurrentOrThrow() + .routerStateSignal().get().location().getQueryParameters() + .getSingleParameter(QUERY_PARAM_FILTER).orElse(""); ---- <1> *Tip:* To improve readability and maintainability, declare query parameter names as constants. -Now, if you navigate to `/orders?sort=date&filter=shipped`, the query parameter values will be: - -* `sort` -> `"date"` -* `filter` -> `"shipped"` +Bind components to these parameter signals as you would to any other signal. Reading the value inside a binding's reactive context makes the component update automatically whenever the parameter changes. -The [classname]`QueryParameters` class provides methods for accessing query parameter values. Familiarize yourself with its API if you frequently use query parameters in your application. +The [classname]`QueryParameters` class provides methods for accessing query parameter values; familiarize yourself with its API if you use query parameters often. Since query parameters are always strings and always optional, they may be missing or contain invalid data, so always supply a default with [methodname]`orElse()` and validate as needed. [NOTE] -You can also retrieve query parameters using [methodname]`UI.getCurrent().getActiveViewLocation().getQueryParameters()`. - -Since query parameters are always strings and optional, they may be empty or contain invalid data. To handle this, you can provide default values, redirect users to a different view, or display an error message. +If you need to inspect query parameters imperatively — for example, to redirect before the view renders — implement [interfacename]`BeforeEnterObserver` and read [methodname]`event.getLocation().getQueryParameters()` inside [methodname]`beforeEnter()`. == Setting Query Parameters @@ -112,37 +95,24 @@ public class OrdersView extends Main implements BeforeEnterObserver { You _cannot_ set query parameters to `null`. To clear a query parameter, exclude it from the [classname]`QueryParameters` object. -=== Updating Query Parameters Dynamically +== Binding a Field to a Query Parameter -Query parameters are often set dynamically, such as when users apply filters or change sort options. In such cases, the view must navigate to itself while updating query parameters. - -When navigating to the _same_ view, the existing view instance is reused. The browser URL updates, and [methodname]`beforeEnter()` is invoked again. - -The following example updates the `filter` query parameter when a user types in a text field: +Query parameters often back a form field directly — a filter, a search term, or a sort option. With the parameter exposed as a signal, you can two-way bind a field to it using [methodname]`bindValue()`: the field shows the current value, and edits write back to the URL. [source,java] ---- -@Route -public class OrdersView extends Main implements BeforeEnterObserver { - - private static final String QUERY_PARAM_SORT = "sort"; - private static final String QUERY_PARAM_FILTER = "filter"; +filterField.bindValue(filterParam, this::setFilter); // <1> -// tag::snippet[] - public OrdersView() { - var filterField = new TextField(); - add(filterField); - - filterField.addValueChangeListener(event -> { - var queryParameters = UI.getCurrent().getActiveViewLocation() - .getQueryParameters() - .merging(QUERY_PARAM_FILTER, event.getValue()); - UI.getCurrent().navigate(OrdersView.class, queryParameters); - }); - } -// end::snippet[] - ... +private void setFilter(String filter) { + var queryParameters = UI.getCurrent().getActiveViewLocation() + .getQueryParameters() + .merging(QUERY_PARAM_FILTER, filter); // <2> + UI.getCurrent().navigate(QueryParameterView.class, queryParameters); } ---- +<1> `filterParam` is the parameter signal from <<#reading-query-parameters-reactively,Reading Query Parameters Reactively>>; [methodname]`setFilter()` is called whenever the field changes. +<2> [methodname]`merging()` replaces only the `filter` parameter and leaves any others in the URL intact. + +When the user edits the field, [methodname]`setFilter()` navigates the view to itself with the updated query parameters. The existing view instance is reused, the browser URL updates, and the router state signal — and therefore the bound field — reflects the new value. The binding works in both directions, so the field also updates if the URL changes by other means, such as the browser's back button. -If the original URL was `/orders?sort=date&filter=shipped`, and the user enters "foobar" in the text field, the URL updates to `/orders?sort=date&filter=foobar`. +[methodname]`merging()` preserves the other parameters: if the URL was `/orders?sort=date&filter=shipped` and the user enters "foobar", it becomes `/orders?sort=date&filter=foobar`. diff --git a/articles/building-apps/views/pass-data/route-parameters.adoc b/articles/building-apps/views/pass-data/route-parameters.adoc index 7c05abf7d9..62c942ca3a 100644 --- a/articles/building-apps/views/pass-data/route-parameters.adoc +++ b/articles/building-apps/views/pass-data/route-parameters.adoc @@ -10,7 +10,7 @@ order: 10 = Route Parameters :toclevels: 2 -In this guide, you'll learn how to create a view that accepts a single route parameter. You'll also explore the differences between optional and wildcard route parameters. +In this guide, you'll learn how to create a view that accepts a single route parameter, holds it in a <> so the UI updates reactively, and keeps the browser URL in sync. You'll also explore the differences between optional and wildcard route parameters. == Copy-Paste into Your Project @@ -22,7 +22,7 @@ If you want to quickly try out route parameters in your Vaadin application, copy include::{root}/src/main/java/com/vaadin/demo/buildingapps/passdata/RouteParameterView.java[tags=snippet,indent=0] ---- -For more detailed instructions on how to use route parameters, continue reading below. +This view stores the route parameter in a signal, binds its text to a value derived from that signal, and updates the URL whenever the signal changes. For more detailed instructions on how to use route parameters, continue reading below. == What Are Route Parameters? @@ -138,3 +138,47 @@ Now, navigating to `/customers/cu1234/edit` passes `"cu1234/edit"` as the parame An empty wildcard parameter is an empty string (`""`), while an empty optional parameter is `null`. So, navigating to `/customers` calls [methodname]`setParameter()` with `""` instead of `null`. If you’re considering wildcard parameters because you need multiple route parameters, <> may be a better solution. + + +== Holding the Parameter in a Signal + +The [classname]`CustomerView` examples above set text directly inside [methodname]`setParameter()`. That works for trivial views, but as soon as several parts of the UI depend on the parameter, you have to remember to update each of them by hand every time the value changes. + +A cleaner approach — the one used by the <<#copy-paste-into-your-project,copy-paste example>> at the top of this guide — is to store the parameter in a <> and let the UI derive from it. The signal becomes the single source of truth: [methodname]`setParameter()` only writes the incoming value to the signal, and every component that depends on the parameter binds to it and updates itself automatically. + +[source,java] +---- +private final ValueSignal parameter = new ValueSignal<>(null); + +@Override +public void setParameter(BeforeEvent event, @OptionalParameter String parameterValue) { + parameter.set(parameterValue); +} +---- + +The parameter is held in a [classname]`ValueSignal`, initialized to `null` for the "no value" case. When the router calls [methodname]`setParameter()`, it writes the incoming value — which may be `null` for an <<#optional-parameters,optional parameter>> — straight to the signal. + +Components don't read the parameter directly. Instead, they bind to the signal and re-render whenever it changes. The copy-paste example binds a [classname]`Paragraph` with [methodname]`bindText()`: + +[source,java] +---- +parameterValue.bindText(() -> Optional.ofNullable(parameter.get()) + .map(value -> "Parameter value: " + value) + .orElse("No parameter value provided")); +---- + +Anything else that depends on the parameter — an enabled state, a visible flag, a nested component — can bind to the same signal in the same way. See <> for the full set of binding methods. + + +== Keeping the URL in Sync + +Because the signal is the source of truth, the URL should follow it: when the value changes, the browser's address bar should update to match. A <> handles this. Reading `parameter.get()` inside the effect makes it re-run whenever the value changes, navigating the view to itself with the current parameter: + +[source,java] +---- +Signal.effect(this, + () -> UI.getCurrent().navigate(RouteParameterView.class, + parameter.get())); +---- + +The effect is bound to the view's lifecycle, so it's active only while the view is attached. Navigating the view to itself reuses the existing instance and updates the URL; because the target location matches the current one whenever [methodname]`setParameter()` was the source of the change, this doesn't cause a navigation loop. diff --git a/articles/building-apps/views/pass-data/route-templates.adoc b/articles/building-apps/views/pass-data/route-templates.adoc index 9cc550f662..08f5d0f96c 100644 --- a/articles/building-apps/views/pass-data/route-templates.adoc +++ b/articles/building-apps/views/pass-data/route-templates.adoc @@ -10,7 +10,7 @@ order: 20 = Route Templates :toclevels: 2 -In this guide, you'll learn how to create a view that accepts multiple route parameters using route templates. You'll also learn how to use modifiers and regular expressions to tweak the behavior of the route parameters. +In this guide, you'll learn how to create a view that accepts multiple route parameters using route templates, and how to read those parameters reactively with a <>. You'll also learn how to use modifiers and regular expressions to tweak the behavior of the route parameters. Using a single <> is easier than using a route template. If you can get the job done using the [interfacename]`HasUrlParameter` interface, use that instead. @@ -24,7 +24,7 @@ If you want to quickly try out route templates in your Vaadin application, copy- include::{root}/src/main/java/com/vaadin/demo/buildingapps/passdata/RouteTemplateView.java[tags=snippet,indent=0] ---- -For more detailed instructions on how to use route templates, continue reading below. +This view reads its route parameters from the router state signal and binds a [classname]`Paragraph` to each, so the displayed values update automatically whenever the URL changes. For more detailed instructions on how to use route templates, continue reading below. == Specifying Multiple Route Parameters @@ -53,30 +53,35 @@ Now, if you navigate to `/customer/cu12345/edit`, the router renders the [classn == Accessing Route Parameter Values -To access the route parameter values, your view must implement the [interfacename]`BeforeEnterObserver` interface. This interface defines the [methodname]`beforeEnter()` method, which is called by the router before navigating to the view. Inside the method, you can access the route parameters: +The current route parameters are available as a <>. Calling [methodname]`UI.getCurrentOrThrow().routerStateSignal()` returns a read-only signal whose value is a [classname]`RouterState` record; its [methodname]`routeParameters()` method gives you the [classname]`RouteParameters` for the active navigation. + +A convenient pattern is to derive a separate signal for each parameter. Because [interfacename]`Signal` is a functional interface, you can define one with a lambda that reads the value from the router state: [source,java] ---- -@Route("customer/:customerId/:action") -public class CustomerView extends Main implements BeforeEnterObserver { +private static final String PARAM_ID = "id"; // <1> +private static final String PARAM_ACTION = "action"; - private static final String PARAM_CUSTOMER_ID = "customerId"; // <1> - private static final String PARAM_ACTION = "action"; - - @Override - public void beforeEnter(BeforeEnterEvent event) { - var customerId = event.getRouteParameters().get(PARAM_CUSTOMER_ID).get(); - var action = event.getRouteParameters().get(PARAM_ACTION).get(); - // Process the parameters - } - ... -} +private final Signal idParam = () -> UI.getCurrentOrThrow() + .routerStateSignal().get().routeParameters().get(PARAM_ID) + .orElse(null); ---- <1> *Tip:* To improve readability and maintainability, declare route parameter names as constants near the [annotationname]`@Route` annotation. -The [classname]`RouteParameters` class defines methods for accessing route parameter values as `String`, `Integer`, or `Long`. They all return an `Optional`. +Bind components to these parameter signals as you would to any other signal — for example, with [methodname]`bindText()`: -In the example above, both `customerId` and `action` are required, so you can assume that those route parameter values are never empty. If you try to navigate to `/customer` or `/customer/cu12345`, the router returns a `404 Not Found` error. +[source,java] +---- +idParagraph.bindText(() -> "ID: " + idParam.get()); +---- + +The value is read inside the binding's reactive context, so the paragraph re-renders automatically whenever the parameter changes — including when the view navigates to itself with new values. The [classname]`RouteParameters` class defines methods for reading values as `String`, `Integer`, or `Long`; they all return an `Optional`. The sections below show just the relevant accessor call on `routeParameters` — that is, the body of each parameter signal's lambda. + +[IMPORTANT] +Read each parameter defensively, supplying a default with [methodname]`orElse()` rather than [methodname]`orElseThrow()`. A binding is first evaluated while the view is being constructed — before the router has populated the state with the navigation's parameters — so even a required parameter is briefly absent. Returning a default keeps that first evaluation harmless, and the binding re-runs with the real value once navigation completes. (A missing _required_ parameter in the URL itself still yields a `404 Not Found`: navigating to `/customer` when the route requires more segments simply fails to match.) + +[NOTE] +The router state signal is read-only. To change the parameters, navigate to the view with new values, as the [methodname]`showView()` method in the copy-paste example does. If you need to inspect parameters imperatively — for example, to redirect before the view renders — implement [interfacename]`BeforeEnterObserver` and read [methodname]`event.getRouteParameters()` inside [methodname]`beforeEnter()`. == Optional Route Parameters @@ -85,27 +90,16 @@ By default, all route parameters are required. To make a route parameter optiona [source,java] ---- -// tag::snippet[] @Route("customer/:customerId/:action?") // <1> -// end::snippet[] -public class CustomerView extends Main implements BeforeEnterObserver { - - private static final String PARAM_CUSTOMER_ID = "customerId"; - private static final String PARAM_ACTION = "action"; - - @Override - public void beforeEnter(BeforeEnterEvent event) { - var customerId = event.getRouteParameters().get(PARAM_CUSTOMER_ID).get(); -// tag::snippet[] - var action = event.getRouteParameters().get(PARAM_ACTION).orElse(null); // <2> -// end::snippet[] - // Process the parameters - } - ... -} ---- <1> The `action` route parameter is now optional. -<2> The route parameter value can now be empty. + +The route parameter value can now be empty, so read it with a default: + +[source,java] +---- +routeParameters.get(PARAM_ACTION).orElse(null) +---- Now, if you navigate to `/customer/cu12345`, the `action` route parameter is empty. You can handle empty parameters by providing a default value, redirecting users, or displaying an error message. // TODO Link to conditional routing guide @@ -126,33 +120,22 @@ To denote a route parameter a wildcard, use the `*` modifier: [source,java] ---- @Route("api/:path*") -public class ApiViewer extends Main implements BeforeEnterObserver { +---- - @Override - public void beforeEnter(BeforeEnterEvent event) { - var path = event.getRouteParameters().get("path").orElse(""); - // Process the path - } -} +Read the captured path as a single string: + +[source,java] +---- +routeParameters.get("path").orElse("") ---- Now, if you navigate to `api/com/vaadin/flow/`, the `path` route parameter has the value `"com/vaadin/flow"`. -You can also access the URL segments captured by a wildcard route parameter individually: +You can also access the URL segments captured by a wildcard route parameter individually with [methodname]`getWildcard()`: [source,java] ---- -@Route("api/:path*") -public class ApiViewer extends Main implements BeforeEnterObserver { - - @Override - public void beforeEnter(BeforeEnterEvent event) { -// tag::snippet[] - List segments = event.getRouteParameters().getWildcard("path"); -// end::snippet[] - // Process the segments - } -} +routeParameters.getWildcard("path") ---- Now, if you navigate to the same URL, the `segments` variable contains the list `["com", "vaadin", "flow"]`. @@ -163,7 +146,7 @@ If a route parameter is missing, `getWildcard()` returns an empty list. == Constraining Route Parameter Values with Regular Expressions -In all the examples discussed, the route parameters accept any value. However, a specific value is often expected for a route parameter and the view should be shown only when that specific value is present in the URL. You can do this by defining a regular expression for the route parameter. This reduces the need for validation and sanitation of route parameter values in the [methodname]`beforeEnter()` method. +In all the examples discussed, the route parameters accept any value. However, a specific value is often expected for a route parameter and the view should be shown only when that specific value is present in the URL. You can do this by defining a regular expression for the route parameter. This reduces the need for validation and sanitation of route parameter values in your view. [NOTE] The syntax of the regular expressions is checked at application startup. If there is an error, the application fails to start. @@ -172,9 +155,7 @@ In the following example, the `customerId` route parameter is constrained to an [source,java] ---- -// tag::snippet[] @Route("customer/:customerId([0-9]{1,9})/:action?(view|edit)") -// end::snippet[] public class CustomerView extends Main { ... } diff --git a/src/main/java/com/vaadin/demo/buildingapps/passdata/QueryParameterView.java b/src/main/java/com/vaadin/demo/buildingapps/passdata/QueryParameterView.java index 27519d0a29..020d866d63 100644 --- a/src/main/java/com/vaadin/demo/buildingapps/passdata/QueryParameterView.java +++ b/src/main/java/com/vaadin/demo/buildingapps/passdata/QueryParameterView.java @@ -3,42 +3,33 @@ import com.vaadin.flow.component.UI; import com.vaadin.flow.component.orderedlayout.VerticalLayout; import com.vaadin.flow.component.textfield.TextField; -import com.vaadin.flow.router.BeforeEnterEvent; -import com.vaadin.flow.router.BeforeEnterObserver; import com.vaadin.flow.router.Route; +import com.vaadin.flow.signals.Signal; // tag::snippet[] @Route("building-apps/pass-data/query-parameter") -public class QueryParameterView extends VerticalLayout - implements BeforeEnterObserver { +public class QueryParameterView extends VerticalLayout { - private final TextField filterField; + private static final String QUERY_PARAM_FILTER = "filter"; + + // Read the query parameter reactively from the router state signal + private final Signal filterParam = () -> UI.getCurrentOrThrow() + .routerStateSignal().get().location().getQueryParameters() + .getSingleParameter(QUERY_PARAM_FILTER).orElse(""); QueryParameterView() { - filterField = new TextField("Filter"); + var filterField = new TextField("Filter"); add(filterField); - filterField.addValueChangeListener(event -> { - // @formatter:off hidden-source-line - var queryParameters = UI.getCurrent() - .getActiveViewLocation() - .getQueryParameters() - .merging("filter", event.getValue()); - UI.getCurrent().navigate( - QueryParameterView.class, - queryParameters); - // @formatter:on hidden-source-line - }); + // Two-way binding: the field shows the current value, and edits + // update the URL through setFilter() + filterField.bindValue(filterParam, this::setFilter); } - @Override - public void beforeEnter(BeforeEnterEvent event) { - // @formatter:off hidden-source-line - event.getLocation() - .getQueryParameters() - .getSingleParameter("filter") - .ifPresent(filterField::setValue); - // @formatter:on hidden-source-line + private void setFilter(String filter) { + var queryParameters = UI.getCurrent().getActiveViewLocation() + .getQueryParameters().merging(QUERY_PARAM_FILTER, filter); + UI.getCurrent().navigate(QueryParameterView.class, queryParameters); } } // end::snippet[] diff --git a/src/main/java/com/vaadin/demo/buildingapps/passdata/RouteParameterView.java b/src/main/java/com/vaadin/demo/buildingapps/passdata/RouteParameterView.java index e57e10a1ae..3aca770468 100644 --- a/src/main/java/com/vaadin/demo/buildingapps/passdata/RouteParameterView.java +++ b/src/main/java/com/vaadin/demo/buildingapps/passdata/RouteParameterView.java @@ -1,5 +1,7 @@ package com.vaadin.demo.buildingapps.passdata; +import java.util.Optional; + import org.jspecify.annotations.Nullable; import com.vaadin.flow.component.UI; @@ -10,31 +12,41 @@ import com.vaadin.flow.router.HasUrlParameter; import com.vaadin.flow.router.OptionalParameter; import com.vaadin.flow.router.Route; +import com.vaadin.flow.signals.Signal; +import com.vaadin.flow.signals.local.ValueSignal; // tag::snippet[] @Route("building-apps/pass-data/route-parameter") public class RouteParameterView extends VerticalLayout implements HasUrlParameter { - private final Paragraph parameterValue; + private final ValueSignal parameter = new ValueSignal<>(null); RouteParameterView() { - parameterValue = new Paragraph(); + Paragraph parameterValue = new Paragraph(); add(parameterValue); + + // Update the value by setting the signal add(new Button("Set Parameter Value", - e -> showView("Hello" + System.currentTimeMillis()))); + e -> parameter.set("Hello" + System.currentTimeMillis()))); + + // Bind the paragraph text to a computed signal + parameterValue.bindText(() -> Optional.ofNullable(parameter.get()) + .map(value -> "Parameter value: " + value) + .orElse("No parameter value provided")); + + // Update the URL parameter when the signal changes + Signal.effect(this, () -> UI.getCurrent() + .navigate(RouteParameterView.class, parameter.get())); } @Override public void setParameter(BeforeEvent event, - @OptionalParameter String parameter) { - if (parameter == null) { - parameterValue.setText("No parameter value provided."); - } else { - parameterValue.setText("Parameter value: " + parameter); - } + @OptionalParameter String parameterValue) { + parameter.set(parameterValue); } + // For use by other views navigating to this view public static void showView(@Nullable String parameterValue) { UI.getCurrent().navigate(RouteParameterView.class, parameterValue); } diff --git a/src/main/java/com/vaadin/demo/buildingapps/passdata/RouteTemplateView.java b/src/main/java/com/vaadin/demo/buildingapps/passdata/RouteTemplateView.java index 9d7bb84c6d..f655276ddd 100644 --- a/src/main/java/com/vaadin/demo/buildingapps/passdata/RouteTemplateView.java +++ b/src/main/java/com/vaadin/demo/buildingapps/passdata/RouteTemplateView.java @@ -4,52 +4,50 @@ import com.vaadin.flow.component.button.Button; import com.vaadin.flow.component.html.Paragraph; import com.vaadin.flow.component.orderedlayout.VerticalLayout; -import com.vaadin.flow.router.BeforeEnterEvent; -import com.vaadin.flow.router.BeforeEnterObserver; import com.vaadin.flow.router.Route; import com.vaadin.flow.router.RouteParam; import com.vaadin.flow.router.RouteParameters; +import com.vaadin.flow.signals.Signal; // tag::snippet[] @Route("building-apps/pass-data/route-template/:id([0-9]{1,9})/:action?(view|edit)") -public class RouteTemplateView extends VerticalLayout - implements BeforeEnterObserver { +public class RouteTemplateView extends VerticalLayout { public enum Action { VIEW, EDIT } - private final Paragraph parameterValues; + private static final String PARAM_ID = "id"; + private static final String PARAM_ACTION = "action"; + + // Read each parameter reactively from the router state signal + private final Signal idParam = () -> UI.getCurrentOrThrow() + .routerStateSignal().get().routeParameters().get(PARAM_ID) + .orElse(null); + + private final Signal actionParam = () -> UI.getCurrentOrThrow() + .routerStateSignal().get().routeParameters().get(PARAM_ACTION) + .map(value -> Action.valueOf(value.toUpperCase())) + .orElse(Action.VIEW); RouteTemplateView() { - parameterValues = new Paragraph(); - add(parameterValues); + Paragraph idParagraph = new Paragraph(); + Paragraph actionParagraph = new Paragraph(); + add(idParagraph, actionParagraph); + add(new Button("Set Parameter Values", e -> showView((int) (Math.random() * 1000), Math.random() < 0.5 ? Action.VIEW : Action.EDIT))); - } - @Override - public void beforeEnter(BeforeEnterEvent event) { - // @formatter:off hidden-source-line - var id = event.getRouteParameters() - .getInteger("id") - .orElseThrow(); - var action = event.getRouteParameters() - .get("action") - .map(a -> Action.valueOf(a.toUpperCase())) - .orElse(Action.VIEW); - // @formatter:on hidden-source-line - parameterValues.setText( - "Parameter values: id = " + id + ", action = " + action); + // Bind the paragraphs to the parameter signals + idParagraph.bindText(() -> "ID: " + idParam.get()); + actionParagraph.bindText(() -> "Action: " + actionParam.get()); } + // For use by other views navigating to this view public static void showView(int id, Action action) { - // @formatter:off hidden-source-line - var parameters = new RouteParameters( - new RouteParam("id", id), - new RouteParam("action", action.name())); - // @formatter:on hidden-source-line + var parameters = new RouteParameters(new RouteParam(PARAM_ID, id), + new RouteParam(PARAM_ACTION, action.name().toLowerCase())); UI.getCurrent().navigate(RouteTemplateView.class, parameters); } }