Skip to content

Commit 98fae38

Browse files
fix: Propagate forceInstantiation and recreateLayoutChain flags through forward/reroute (#23848) (CP: 25.1) (#23954) (CP: 25.0) (#23961)
* fix: Propagate forceInstantiation and recreateLayoutChain flags through forward/reroute (#23848) (#23954) * fix compilation error * fix compilation error --------- Co-authored-by: Marco Collovati <marco@vaadin.com>
1 parent f9b11ee commit 98fae38

File tree

8 files changed

+403
-3
lines changed

8 files changed

+403
-3
lines changed

flow-server/src/main/java/com/vaadin/flow/router/ErrorNavigationEvent.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,35 @@ public ErrorNavigationEvent(Router router, Location location, UI ui,
7777
this.errorParameter = errorParameter;
7878
}
7979

80+
/**
81+
* Creates a new navigation event with force instantiation flags.
82+
*
83+
* @param router
84+
* the router handling the navigation, not {@code null}
85+
* @param location
86+
* the new location, not {@code null}
87+
* @param ui
88+
* the UI in which the navigation occurs, not {@code null}
89+
* @param trigger
90+
* the type of user action that triggered this navigation event,
91+
* not {@code null}
92+
* @param errorParameter
93+
* parameter containing navigation error information
94+
* @param forceInstantiation
95+
* if set to {@code true}, the navigation target will always be
96+
* instantiated
97+
* @param recreateLayoutChain
98+
* if set to {@code true}, the complete layout chain up to the
99+
* navigation target will be re-instantiated
100+
*/
101+
public ErrorNavigationEvent(Router router, Location location, UI ui,
102+
NavigationTrigger trigger, ErrorParameter<?> errorParameter,
103+
boolean forceInstantiation, boolean recreateLayoutChain) {
104+
super(router, location, ui, trigger, null, false, forceInstantiation,
105+
recreateLayoutChain);
106+
this.errorParameter = errorParameter;
107+
}
108+
80109
/**
81110
* Gets the ErrorParameter if set.
82111
*

flow-server/src/main/java/com/vaadin/flow/router/internal/AbstractNavigationStateRenderer.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -931,7 +931,9 @@ private NavigationEvent getNavigationEvent(NavigationEvent event,
931931

932932
return new ErrorNavigationEvent(event.getSource(),
933933
event.getLocation(), event.getUI(),
934-
NavigationTrigger.PROGRAMMATIC, errorParameter);
934+
NavigationTrigger.PROGRAMMATIC, errorParameter,
935+
event.isForceInstantiation(),
936+
event.isRecreateLayoutChain());
935937
}
936938

937939
String url;
@@ -972,7 +974,8 @@ private NavigationEvent getNavigationEvent(NavigationEvent event,
972974
Location location = new Location(url, queryParameters);
973975

974976
return new NavigationEvent(event.getSource(), location, event.getUI(),
975-
NavigationTrigger.PROGRAMMATIC, (BaseJsonNode) null, true);
977+
NavigationTrigger.PROGRAMMATIC, (BaseJsonNode) null, true,
978+
event.isForceInstantiation(), event.isRecreateLayoutChain());
976979
}
977980

978981
/**

flow-server/src/main/java/com/vaadin/flow/router/internal/InternalRedirectHandler.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ public int handle(NavigationEvent event) {
5555
}
5656

5757
return router.navigate(ui, target, event.getTrigger(),
58-
event.getState().orElse(null));
58+
event.getState().orElse(null), event.isForceInstantiation(),
59+
event.isRecreateLayoutChain());
5960
}
6061
}

flow-server/src/test/java/com/vaadin/flow/router/internal/NavigationStateRendererTest.java

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,10 @@ public void init() {
163163
RouteRegistry registry = ApplicationRouteRegistry
164164
.getInstance(new MockVaadinContext());
165165
router = new Router(registry);
166+
167+
RouteParentLayout.creationCount.set(0);
168+
ConditionalForwardView.shouldForward = false;
169+
ConditionalRerouteView.shouldReroute = false;
166170
}
167171

168172
@Test
@@ -299,7 +303,10 @@ public DeploymentConfiguration getConfiguration() {
299303
@Tag("div")
300304
private static class RouteParentLayout extends Component
301305
implements RouterLayout {
306+
private static final AtomicInteger creationCount = new AtomicInteger(0);
307+
302308
RouteParentLayout() {
309+
creationCount.incrementAndGet();
303310
addAttachListener(e -> layoutAttachCount.getAndIncrement());
304311
layoutUUID = UUID.randomUUID().toString();
305312
}
@@ -324,6 +331,44 @@ private static class SingleView extends Component {
324331
}
325332
}
326333

334+
@Route(value = "forward-target", layout = RouteParentLayout.class)
335+
@Tag("div")
336+
private static class ForwardTargetView extends Component {
337+
}
338+
339+
@Route(value = "reroute-target", layout = RouteParentLayout.class)
340+
@Tag("div")
341+
private static class RerouteTargetView extends Component {
342+
}
343+
344+
@Route(value = "conditional-forward", layout = RouteParentLayout.class)
345+
@Tag("div")
346+
private static class ConditionalForwardView extends Component
347+
implements BeforeEnterObserver {
348+
static boolean shouldForward = false;
349+
350+
@Override
351+
public void beforeEnter(BeforeEnterEvent event) {
352+
if (shouldForward) {
353+
event.forwardTo(ForwardTargetView.class);
354+
}
355+
}
356+
}
357+
358+
@Route(value = "conditional-reroute", layout = RouteParentLayout.class)
359+
@Tag("div")
360+
private static class ConditionalRerouteView extends Component
361+
implements BeforeEnterObserver {
362+
static boolean shouldReroute = false;
363+
364+
@Override
365+
public void beforeEnter(BeforeEnterEvent event) {
366+
if (shouldReroute) {
367+
event.rerouteTo(RerouteTargetView.class);
368+
}
369+
}
370+
}
371+
327372
@Route(value = "/:samplePersonID?/:action?(edit)")
328373
@RouteAlias(value = "")
329374
@Tag("div")
@@ -758,6 +803,80 @@ public void handle_normalView_refreshCurrentRouteRecreatesComponents() {
758803

759804
}
760805

806+
@Test
807+
public void handle_refreshCurrentRoute_withForwardTo_recreatesComponents() {
808+
layoutAttachCount = new AtomicInteger();
809+
viewAttachCount = new AtomicInteger();
810+
811+
MockVaadinServletService service = createMockServiceWithInstantiator();
812+
MockVaadinSession session = new AlwaysLockedVaadinSession(service);
813+
814+
router = session.getService().getRouter();
815+
NavigationStateRenderer renderer = new NavigationStateRenderer(
816+
new NavigationStateBuilder(router)
817+
.withTarget(ConditionalForwardView.class)
818+
.withPath("conditional-forward").build());
819+
router.getRegistry().setRoute("conditional-forward",
820+
ConditionalForwardView.class, List.of(RouteParentLayout.class));
821+
router.getRegistry().setRoute("forward-target", ForwardTargetView.class,
822+
List.of(RouteParentLayout.class));
823+
824+
MockUI ui = new MockUI(session);
825+
826+
// Initial navigation without forward
827+
renderer.handle(
828+
new NavigationEvent(router, new Location("conditional-forward"),
829+
ui, NavigationTrigger.PAGE_LOAD));
830+
831+
ui.getInternals().clearLastHandledNavigation();
832+
833+
// Enable forwarding and refresh with recreateLayoutChain=true
834+
RouteParentLayout.creationCount.set(0);
835+
ConditionalForwardView.shouldForward = true;
836+
ui.refreshCurrentRoute(true);
837+
838+
Assert.assertEquals(
839+
"Layout should be recreated by both refresh and forward", 2,
840+
RouteParentLayout.creationCount.get());
841+
}
842+
843+
@Test
844+
public void handle_refreshCurrentRoute_withRerouteTo_recreatesComponents() {
845+
layoutAttachCount = new AtomicInteger();
846+
viewAttachCount = new AtomicInteger();
847+
848+
MockVaadinServletService service = createMockServiceWithInstantiator();
849+
MockVaadinSession session = new AlwaysLockedVaadinSession(service);
850+
851+
router = session.getService().getRouter();
852+
NavigationStateRenderer renderer = new NavigationStateRenderer(
853+
new NavigationStateBuilder(router)
854+
.withTarget(ConditionalRerouteView.class)
855+
.withPath("conditional-reroute").build());
856+
router.getRegistry().setRoute("conditional-reroute",
857+
ConditionalRerouteView.class, List.of(RouteParentLayout.class));
858+
router.getRegistry().setRoute("reroute-target", RerouteTargetView.class,
859+
List.of(RouteParentLayout.class));
860+
861+
MockUI ui = new MockUI(session);
862+
863+
// Initial navigation without reroute
864+
renderer.handle(
865+
new NavigationEvent(router, new Location("conditional-reroute"),
866+
ui, NavigationTrigger.PAGE_LOAD));
867+
868+
ui.getInternals().clearLastHandledNavigation();
869+
870+
// Enable rerouting and refresh with recreateLayoutChain=true
871+
RouteParentLayout.creationCount.set(0);
872+
ConditionalRerouteView.shouldReroute = true;
873+
ui.refreshCurrentRoute(true);
874+
875+
Assert.assertEquals(
876+
"Layout should be recreated by both refresh and reroute", 2,
877+
RouteParentLayout.creationCount.get());
878+
}
879+
761880
@Test
762881
public void handle_clientNavigation_withMatchingFlowRoute() {
763882
viewAttachCount = new AtomicInteger();

flow-tests/test-root-context/src/main/java/com/vaadin/flow/uitest/ui/RefreshCurrentRouteLayout.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,17 @@
1717

1818
import java.util.UUID;
1919

20+
import com.vaadin.flow.component.ComponentUtil;
21+
import com.vaadin.flow.component.UI;
2022
import com.vaadin.flow.component.html.Div;
2123
import com.vaadin.flow.dom.Element;
2224
import com.vaadin.flow.router.RouterLayout;
25+
import com.vaadin.flow.uitest.ui.RefreshCurrentRouteRedirectView.RedirectData;
2326

2427
public class RefreshCurrentRouteLayout implements RouterLayout {
2528

2629
final static String ROUTER_LAYOUT_ID = "routerlayoutid";
30+
final static String LAYOUT_CREATION_COUNT_ID = "layout-creation-count";
2731

2832
private Div layout = new Div();
2933

@@ -32,6 +36,18 @@ public RefreshCurrentRouteLayout() {
3236
Div routerLayoutId = new Div(uniqueId);
3337
routerLayoutId.setId(ROUTER_LAYOUT_ID);
3438
layout.add(routerLayoutId);
39+
40+
UI ui = UI.getCurrent();
41+
if (ui != null) {
42+
RedirectData data = ComponentUtil.getData(ui, RedirectData.class);
43+
if (data != null) {
44+
data.layoutCreationCount++;
45+
Div countDiv = new Div(
46+
String.valueOf(data.layoutCreationCount));
47+
countDiv.setId(LAYOUT_CREATION_COUNT_ID);
48+
layout.add(countDiv);
49+
}
50+
}
3551
}
3652

3753
@Override
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright 2000-2026 Vaadin Ltd.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
5+
* use this file except in compliance with the License. You may obtain a copy of
6+
* the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations under
14+
* the License.
15+
*/
16+
package com.vaadin.flow.uitest.ui;
17+
18+
import java.util.UUID;
19+
20+
import com.vaadin.flow.component.html.Div;
21+
import com.vaadin.flow.router.Route;
22+
23+
@Route(value = "com.vaadin.flow.uitest.ui.RefreshCurrentRouteRedirectTargetView", layout = RefreshCurrentRouteLayout.class)
24+
public class RefreshCurrentRouteRedirectTargetView extends Div {
25+
26+
static final String VIEW_ID = "forward-target-id";
27+
28+
public RefreshCurrentRouteRedirectTargetView() {
29+
Div id = new Div(UUID.randomUUID().toString());
30+
id.setId(VIEW_ID);
31+
add(id);
32+
}
33+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
* Copyright 2000-2026 Vaadin Ltd.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
5+
* use this file except in compliance with the License. You may obtain a copy of
6+
* the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations under
14+
* the License.
15+
*/
16+
package com.vaadin.flow.uitest.ui;
17+
18+
import com.vaadin.flow.component.ComponentUtil;
19+
import com.vaadin.flow.component.UI;
20+
import com.vaadin.flow.component.html.Div;
21+
import com.vaadin.flow.component.html.NativeButton;
22+
import com.vaadin.flow.router.BeforeEnterEvent;
23+
import com.vaadin.flow.router.BeforeEnterObserver;
24+
import com.vaadin.flow.router.Route;
25+
26+
@Route(value = "com.vaadin.flow.uitest.ui.RefreshCurrentRouteRedirectView", layout = RefreshCurrentRouteLayout.class)
27+
public class RefreshCurrentRouteRedirectView extends Div
28+
implements BeforeEnterObserver {
29+
30+
enum RedirectMode {
31+
FORWARD, REROUTE
32+
}
33+
34+
static class RedirectData {
35+
RedirectMode mode;
36+
int layoutCreationCount;
37+
38+
RedirectData(RedirectMode mode) {
39+
this.mode = mode;
40+
this.layoutCreationCount = 0;
41+
}
42+
}
43+
44+
static final String FORWARD_AND_REFRESH_LAYOUTS = "forward-refresh-layouts";
45+
static final String FORWARD_AND_REFRESH = "forward-refresh";
46+
static final String REROUTE_AND_REFRESH_LAYOUTS = "reroute-refresh-layouts";
47+
static final String REROUTE_AND_REFRESH = "reroute-refresh";
48+
49+
@Override
50+
public void beforeEnter(BeforeEnterEvent event) {
51+
RedirectData data = ComponentUtil.getData(event.getUI(),
52+
RedirectData.class);
53+
if (data == null) {
54+
return;
55+
}
56+
switch (data.mode) {
57+
case FORWARD ->
58+
event.forwardTo(RefreshCurrentRouteRedirectTargetView.class);
59+
case REROUTE ->
60+
event.rerouteTo(RefreshCurrentRouteRedirectTargetView.class);
61+
}
62+
}
63+
64+
public RefreshCurrentRouteRedirectView() {
65+
addButton(FORWARD_AND_REFRESH_LAYOUTS, "Forward + refresh layouts",
66+
RedirectMode.FORWARD, true);
67+
addButton(FORWARD_AND_REFRESH, "Forward + refresh view only",
68+
RedirectMode.FORWARD, false);
69+
addButton(REROUTE_AND_REFRESH_LAYOUTS, "Reroute + refresh layouts",
70+
RedirectMode.REROUTE, true);
71+
addButton(REROUTE_AND_REFRESH, "Reroute + refresh view only",
72+
RedirectMode.REROUTE, false);
73+
}
74+
75+
private void addButton(String id, String text, RedirectMode mode,
76+
boolean recreateLayouts) {
77+
NativeButton button = new NativeButton(text, e -> {
78+
UI ui = UI.getCurrent();
79+
ComponentUtil.setData(ui, RedirectData.class,
80+
new RedirectData(mode));
81+
ui.refreshCurrentRoute(recreateLayouts);
82+
});
83+
button.setId(id);
84+
add(button);
85+
}
86+
}

0 commit comments

Comments
 (0)