diff --git a/flow-server/src/main/java/com/vaadin/flow/component/UI.java b/flow-server/src/main/java/com/vaadin/flow/component/UI.java index 0830da716da..b7194aad023 100644 --- a/flow-server/src/main/java/com/vaadin/flow/component/UI.java +++ b/flow-server/src/main/java/com/vaadin/flow/component/UI.java @@ -1053,6 +1053,11 @@ public Page getPage() { * If the view change actually happens (e.g. the view itself doesn't cancel * the navigation), all navigation listeners are notified and a reference of * the new view is returned for additional configuration. + *

+ * Navigating to the location that is already shown does nothing: the view + * is not re-instantiated and no navigation lifecycle events are fired. Use + * {@link #refreshCurrentRoute(boolean)} to rebuild the view that is + * currently shown. * * @param * the navigation target type @@ -1064,9 +1069,14 @@ public Page getPage() { * @throws NotFoundException * in case there is no route defined for the given * navigationTarget. - * @return the view instance, if navigation actually happened + * @return the requested view instance, if it is part of the active route + * chain after this call. When the navigation was skipped because + * that view was already shown, this is the existing instance; when + * the navigation was rerouted or forwarded elsewhere, the result is + * empty. * @see #navigate(Class, Object) * @see #navigate(Class, RouteParameters) + * @see #refreshCurrentRoute(boolean) */ public Optional navigate( Class navigationTarget) { @@ -1100,6 +1110,11 @@ private Optional findCurrentNavigationTarget( * If the view change actually happens (e.g. the view itself doesn't cancel * the navigation), all navigation listeners are notified and a reference of * the new view is returned for additional configuration. + *

+ * Navigating to the location that is already shown does nothing: the view + * is not re-instantiated and no navigation lifecycle events are fired. Use + * {@link #refreshCurrentRoute(boolean)} to rebuild the view that is + * currently shown. * * @param navigationTarget * navigation target to navigate to @@ -1109,7 +1124,11 @@ private Optional findCurrentNavigationTarget( * url parameter type * @param * navigation target type - * @return the view instance, if navigation actually happened + * @return the requested view instance, if it is part of the active route + * chain after this call. When the navigation was skipped because + * that view was already shown, this is the existing instance; when + * the navigation was rerouted or forwarded elsewhere, the result is + * empty. * @throws IllegalArgumentException * if a {@code null} parameter is given while navigationTarget's * parameter is not annotated with @OptionalParameter @@ -1117,6 +1136,7 @@ private Optional findCurrentNavigationTarget( * @throws NotFoundException * in case there is no route defined for the given * navigationTarget matching the parameters. + * @see #refreshCurrentRoute(boolean) */ @SuppressWarnings("unchecked") public > Optional navigate( @@ -1140,6 +1160,11 @@ public > Optional navigate( * If the view change actually happens (e.g. the view itself doesn't cancel * the navigation), all navigation listeners are notified and a reference of * the new view is returned for additional configuration. + *

+ * Navigating to the location that is already shown does nothing: the view + * is not re-instantiated and no navigation lifecycle events are fired. Use + * {@link #refreshCurrentRoute(boolean)} to rebuild the view that is + * currently shown. * * @param * the navigation target type @@ -1147,7 +1172,11 @@ public > Optional navigate( * navigation target to navigate to. * @param parameters * parameters to pass to view. - * @return the view instance, if navigation actually happened + * @return the requested view instance, if it is part of the active route + * chain after this call. When the navigation was skipped because + * that view was already shown, this is the existing instance; when + * the navigation was rerouted or forwarded elsewhere, the result is + * empty. * @throws IllegalArgumentException * if navigationTarget is a {@link HasUrlParameter} with a * mandatory parameter, but parameters argument doesn't provide @@ -1156,6 +1185,7 @@ public > Optional navigate( * in case there is no route defined for the given * navigationTarget matching the parameters. * @since 4.0 + * @see #refreshCurrentRoute(boolean) */ public Optional navigate(Class navigationTarget, RouteParameters parameters) { @@ -1179,6 +1209,11 @@ public Optional navigate(Class navigationTarget, * If the view change actually happens (e.g. the view itself doesn't cancel * the navigation), all navigation listeners are notified and a reference of * the new view is returned for additional configuration. + *

+ * Navigating to the location that is already shown does nothing: the view + * is not re-instantiated and no navigation lifecycle events are fired. Use + * {@link #refreshCurrentRoute(boolean)} to rebuild the view that is + * currently shown. * * @param * the navigation target type @@ -1186,7 +1221,11 @@ public Optional navigate(Class navigationTarget, * navigation target to navigate to. * @param parameters * parameters to pass to view. - * @return the view instance, if navigation actually happened + * @return the requested view instance, if it is part of the active route + * chain after this call. When the navigation was skipped because + * that view was already shown, this is the existing instance; when + * the navigation was rerouted or forwarded elsewhere, the result is + * empty. * @throws IllegalArgumentException * if navigationTarget is a {@link HasUrlParameter} with a * mandatory parameter, but parameters argument doesn't provide @@ -1195,6 +1234,7 @@ public Optional navigate(Class navigationTarget, * in case there is no route defined for the given * navigationTarget matching the parameters. * @since 24.1.1 + * @see #refreshCurrentRoute(boolean) */ public Optional navigate(Class navigationTarget, RouteParam... parameters) { @@ -1216,6 +1256,11 @@ public Optional navigate(Class navigationTarget, * If the view change actually happens (e.g. the view itself doesn't cancel * the navigation), all navigation listeners are notified and a reference of * the new view is returned for additional configuration. + *

+ * Navigating to the location that is already shown does nothing: the view + * is not re-instantiated and no navigation lifecycle events are fired. Use + * {@link #refreshCurrentRoute(boolean)} to rebuild the view that is + * currently shown. * * @param navigationTarget * navigation target to navigate to @@ -1227,7 +1272,11 @@ public Optional navigate(Class navigationTarget, * url parameter type * @param * navigation target type - * @return the view instance, if navigation actually happened + * @return the requested view instance, if it is part of the active route + * chain after this call. When the navigation was skipped because + * that view was already shown, this is the existing instance; when + * the navigation was rerouted or forwarded elsewhere, the result is + * empty. * @throws IllegalArgumentException * if a {@code null} parameter is given while navigationTarget's * parameter is not annotated with @OptionalParameter @@ -1236,6 +1285,7 @@ public Optional navigate(Class navigationTarget, * in case there is no route defined for the given * navigationTarget matching the parameters. * @since 23.2 + * @see #refreshCurrentRoute(boolean) */ @SuppressWarnings("unchecked") public > Optional navigate( @@ -1268,6 +1318,11 @@ public > Optional navigate( * If the view change actually happens (e.g. the view itself doesn't cancel * the navigation), all navigation listeners are notified and a reference of * the new view is returned for additional configuration. + *

+ * Navigating to the location that is already shown does nothing: the view + * is not re-instantiated and no navigation lifecycle events are fired. Use + * {@link #refreshCurrentRoute(boolean)} to rebuild the view that is + * currently shown. * * @param navigationTarget * navigation target to navigate to @@ -1277,7 +1332,11 @@ public > Optional navigate( * additional query parameters to pass to view * @param * navigation target type - * @return the view instance, if navigation actually happened + * @return the requested view instance, if it is part of the active route + * chain after this call. When the navigation was skipped because + * that view was already shown, this is the existing instance; when + * the navigation was rerouted or forwarded elsewhere, the result is + * empty. * @throws IllegalArgumentException * if a {@code null} parameter is given while navigationTarget's * parameter is not annotated with @OptionalParameter @@ -1286,6 +1345,7 @@ public > Optional navigate( * in case there is no route defined for the given * navigationTarget matching the parameters. * @since 24.1 + * @see #refreshCurrentRoute(boolean) */ @SuppressWarnings("unchecked") public Optional navigate( @@ -1310,6 +1370,11 @@ public Optional navigate( * If the view change actually happens (e.g. the view itself doesn't cancel * the navigation), all navigation listeners are notified and a reference of * the new view is returned for additional configuration. + *

+ * Navigating to the location that is already shown does nothing: the view + * is not re-instantiated and no navigation lifecycle events are fired. Use + * {@link #refreshCurrentRoute(boolean)} to rebuild the view that is + * currently shown. * * @param navigationTarget * navigation target to navigate to @@ -1317,11 +1382,16 @@ public Optional navigate( * additional query parameters to pass to view * @param * navigation target type - * @return the view instance, if navigation actually happened + * @return the requested view instance, if it is part of the active route + * chain after this call. When the navigation was skipped because + * that view was already shown, this is the existing instance; when + * the navigation was rerouted or forwarded elsewhere, the result is + * empty. * @throws NotFoundException * in case there is no route defined for the given * navigationTarget matching the parameters. * @since 23.2 + * @see #refreshCurrentRoute(boolean) */ @SuppressWarnings("unchecked") public Optional navigate( @@ -1351,9 +1421,15 @@ public Optional navigate( *

* Besides the navigation to the {@code location} this method also updates * the browser location (and page history). + *

+ * Navigating to the location that is already shown does nothing: the view + * is not re-instantiated and no navigation lifecycle events are fired. Use + * {@link #refreshCurrentRoute(boolean)} to rebuild the view that is + * currently shown. * * @see #navigate(String, QueryParameters) * @see Router#navigate(UI, Location, NavigationTrigger) + * @see #refreshCurrentRoute(boolean) * * @param location * the location to navigate to, not {@code null} @@ -1371,9 +1447,15 @@ public void navigate(String location) { *

* Besides the navigation to the {@code location} this method also updates * the browser location (and page history). + *

+ * Navigating to the location that is already shown does nothing: the view + * is not re-instantiated and no navigation lifecycle events are fired. Use + * {@link #refreshCurrentRoute(boolean)} to rebuild the view that is + * currently shown. * * @see #navigate(String) * @see Router#navigate(UI, Location, NavigationTrigger) + * @see #refreshCurrentRoute(boolean) * * @param locationString * the location to navigate to, not {@code null} @@ -1454,6 +1536,10 @@ public void navigate(String locationString, * Re-navigates to the current route. Also re-instantiates the route target * component, and optionally all layouts in the route chain. *

+ * This is the way to rebuild the view that is currently shown, since + * {@link #navigate(Class) navigating} to the location that is already shown + * does nothing. + *

* In development mode, local signal field values are automatically * transferred from the old component instance to the new one, preserving UI * state across refreshes. @@ -1462,6 +1548,7 @@ public void navigate(String locationString, * {@code true} to refresh all layouts in the route chain, * {@code false} to only refresh the route instance * @since 24.4 + * @see #navigate(Class) */ public void refreshCurrentRoute(boolean refreshRouteChain) { getInternals().refreshCurrentRoute(refreshRouteChain); diff --git a/flow-server/src/test/java/com/vaadin/flow/component/UITest.java b/flow-server/src/test/java/com/vaadin/flow/component/UITest.java index 51675661b7a..0c034b94e9a 100644 --- a/flow-server/src/test/java/com/vaadin/flow/component/UITest.java +++ b/flow-server/src/test/java/com/vaadin/flow/component/UITest.java @@ -94,6 +94,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -444,6 +445,29 @@ public void locationAfterServerNavigation() CoreMatchers.instanceOf(FooBarNavigationTarget.class)); } + @Test + public void navigateToShownView_notReinstantiated_shownInstanceReturned() + throws InvalidRouteConfigurationException { + UI ui = new UI(); + initUI(ui, "", null); + + FooBarNavigationTarget shownView = ui + .navigate(FooBarNavigationTarget.class).orElseThrow(); + + // Navigating to the location that is already shown does nothing, so + // the returned Optional holds the view that is already shown + FooBarNavigationTarget sameView = ui + .navigate(FooBarNavigationTarget.class).orElseThrow(); + + assertSame(shownView, sameView, + "Navigating to the shown view should not re-instantiate it"); + + ui.refreshCurrentRoute(false); + + assertNotSame(shownView, ui.getCurrentView(), + "refreshCurrentRoute should re-instantiate the shown view"); + } + @Test @Disabled("Check what is the new Router.navigate for JavaScriptUI") public void navigateWithParameters_delegateToRouter() {