diff --git a/flow-server/src/main/java/com/vaadin/flow/router/internal/AbstractNavigationStateRenderer.java b/flow-server/src/main/java/com/vaadin/flow/router/internal/AbstractNavigationStateRenderer.java
index 86092096d7f..aa74da5d6bd 100644
--- a/flow-server/src/main/java/com/vaadin/flow/router/internal/AbstractNavigationStateRenderer.java
+++ b/flow-server/src/main/java/com/vaadin/flow/router/internal/AbstractNavigationStateRenderer.java
@@ -114,7 +114,7 @@ public NavigationState getNavigationState() {
*
* Override this method to control the creation of view instances.
*
- * By default always creates new instances.
+ * By default, always creates new instances.
*
* @param
* the route target type
@@ -168,56 +168,25 @@ public int handle(NavigationEvent event) {
clearContinueNavigationAction(ui);
checkForDuplicates(routeTargetType, routeLayoutTypes);
- BeforeLeaveEvent beforeNavigationDeactivating = new BeforeLeaveEvent(
- event, routeTargetType, parameters, routeLayoutTypes);
-
- Optional result = executeBeforeLeaveNavigation(event,
- beforeNavigationDeactivating);
+ Optional result = handleBeforeLeaveEvents(event,
+ routeTargetType, parameters);
if (result.isPresent()) {
return result.get();
}
- // If navigation target is Hilla route, terminate Flow navigation logic
- // here.
- String route = event.getLocation().getPath().isEmpty()
- ? event.getLocation().getPath()
- : event.getLocation().getPath().startsWith("/")
- ? event.getLocation().getPath()
- : "/" + event.getLocation().getPath();
- if (MenuRegistry.hasClientRoute(route, true) && !MenuRegistry
- .getClientRoutes(true).get(route).flowLayout()) {
+ String route = getFormattedRoute(event);
+ if (isClientHandled(route)) {
return HttpStatusCode.OK.getCode();
}
- final ArrayList chain;
+ final ArrayList chain = new ArrayList<>();
final boolean preserveOnRefreshTarget = isPreserveOnRefreshTarget(
routeTargetType, routeLayoutTypes);
- if (preserveOnRefreshTarget && !event.isForceInstantiation()) {
- final Optional> maybeChain = getPreservedChain(
- event);
- if (maybeChain.isEmpty()) {
- // We're returning because the preserved chain is not ready to
- // be used as is, and requires client data requested within
- // `getPreservedChain`. Once the data is retrieved from the
- // client, `handle` method will be invoked with the same
- // `NavigationEvent` argument.
- return HttpStatusCode.OK.getCode();
- } else {
- chain = maybeChain.get();
- }
- } else {
-
- // Create an empty chain which gets populated later in
- // `createChainIfEmptyAndExecuteBeforeEnterNavigation`.
- chain = new ArrayList<>();
-
- // Has any preserved components already been created here? If so,
- // we don't want to navigate back to them ever so clear cache for
- // window.
- clearAllPreservedChains(ui);
+ if (populateChain(chain, preserveOnRefreshTarget, event)) {
+ return HttpStatusCode.OK.getCode();
}
// Set navigationTrigger to RELOAD if this is a refresh of a preserve
@@ -234,11 +203,8 @@ public int handle(NavigationEvent event) {
// See https://github.com/vaadin/flow/issues/3619 for more info.
pushHistoryStateIfNeeded(event, ui);
- BeforeEnterEvent beforeNavigationActivating = new BeforeEnterEvent(
- event, routeTargetType, parameters, routeLayoutTypes);
-
- result = createChainIfEmptyAndExecuteBeforeEnterNavigation(
- beforeNavigationActivating, event, chain);
+ result = handleBeforeNavigationEvents(event, routeTargetType,
+ parameters, chain);
if (result.isPresent()) {
return result.get();
}
@@ -255,18 +221,7 @@ public int handle(NavigationEvent event) {
List routerLayouts = (List) (List>) chain
.subList(1, chain.size());
- // If a route refresh has been requested, remove all modal components.
- // This is necessary because maintaining the correct modality
- // cardinality and order is not feasible without knowing who opened them
- // and when.
- if (ui.hasModalComponent()
- && event.getTrigger() == NavigationTrigger.REFRESH_ROUTE) {
- Component modalComponent;
- while ((modalComponent = ui.getInternals()
- .getActiveModalComponent()) != null) {
- modalComponent.removeFromParent();
- }
- }
+ cleanModalComponents(event);
// Change the UI according to the navigation Component chain.
ui.getInternals().showRouteTarget(event.getLocation(),
@@ -276,6 +231,109 @@ public int handle(NavigationEvent event) {
validateStatusCode(statusCode, routeTargetType);
// After navigation event
+ handleAfterNavigationEvents(ui, parameters);
+
+ updatePageTitle(event, componentInstance, route);
+
+ return statusCode;
+ }
+
+ /**
+ * Populate element chain from a preserved chain or give clean chain to be
+ * populated.
+ *
+ * @param chain
+ * chain to populate
+ * @param preserveOnRefreshTarget
+ * preserve on refresh boolean
+ * @param event
+ * current navigation event
+ * @return {@code true} if additional client data requested, else
+ * {@code false}
+ */
+ private boolean populateChain(ArrayList chain,
+ boolean preserveOnRefreshTarget, NavigationEvent event) {
+ if (preserveOnRefreshTarget && !event.isForceInstantiation()) {
+ final Optional> maybeChain = getPreservedChain(
+ event);
+ if (maybeChain.isEmpty()) {
+ // We're returning because the preserved chain is not ready to
+ // be used as is, and requires client data requested within
+ // `getPreservedChain`. Once the data is retrieved from the
+ // client, `handle` method will be invoked with the same
+ // `NavigationEvent` argument.
+ return true;
+ }
+ chain.addAll(maybeChain.get());
+ } else {
+ // Create an empty chain which gets populated later in
+ // `createChainIfEmptyAndExecuteBeforeEnterNavigation`.
+ chain.clear();
+
+ // Has any preserved components already been created here? If so,
+ // we don't want to navigate back to them ever so clear cache for
+ // window.
+ clearAllPreservedChains(event.getUI());
+ }
+ return false;
+ }
+
+ /**
+ * Send before leave event to all listeners.
+ *
+ * @return optional return http status code
+ */
+ private Optional handleBeforeLeaveEvents(NavigationEvent event,
+ Class extends Component> routeTargetType,
+ RouteParameters parameters) {
+ BeforeLeaveEvent beforeNavigationDeactivating = new BeforeLeaveEvent(
+ event, routeTargetType, parameters, routeLayoutTypes);
+
+ return executeBeforeLeaveNavigation(event,
+ beforeNavigationDeactivating);
+ }
+
+ /**
+ * If a route refresh has been requested, remove all modal components. This
+ * is necessary because maintaining the correct modality cardinality and
+ * order is not feasible without knowing who opened them and when.
+ *
+ * @param event
+ * navigation event
+ */
+ private static void cleanModalComponents(NavigationEvent event) {
+ if (event.getUI().hasModalComponent()
+ && event.getTrigger() == NavigationTrigger.REFRESH_ROUTE) {
+ Component modalComponent;
+ while ((modalComponent = event.getUI().getInternals()
+ .getActiveModalComponent()) != null) {
+ modalComponent.removeFromParent();
+ }
+ }
+ }
+
+ /**
+ * Send before navigation event to all listeners.
+ *
+ * @return optional return http status code
+ */
+ private Optional handleBeforeNavigationEvents(
+ NavigationEvent event, Class extends Component> routeTargetType,
+ RouteParameters parameters, ArrayList chain) {
+ BeforeEnterEvent beforeNavigationActivating = new BeforeEnterEvent(
+ event, routeTargetType, parameters, routeLayoutTypes);
+
+ return createChainIfEmptyAndExecuteBeforeEnterNavigation(
+ beforeNavigationActivating, event, chain);
+ }
+
+ /**
+ * Send after navigation event to all listeners.
+ *
+ * @return optional return http status code
+ */
+ private void handleAfterNavigationEvents(UI ui,
+ RouteParameters parameters) {
List afterNavigationHandlers = new ArrayList<>(
ui.getNavigationListeners(AfterNavigationHandler.class));
afterNavigationHandlers
@@ -284,10 +342,36 @@ public int handle(NavigationEvent event) {
fireAfterNavigationListeners(
new AfterNavigationEvent(locationChangeEvent, parameters),
afterNavigationHandlers);
+ }
- updatePageTitle(event, componentInstance, route);
+ /**
+ * Check if target route is client handled and Flow should not handle
+ * rendering.
+ *
+ * @param route
+ * formatted route target string
+ * @return {@code true} if client handled render for route
+ */
+ private boolean isClientHandled(String route) {
+ // If navigation target is Hilla route, terminate Flow navigation logic
+ // here.
+ return MenuRegistry.hasClientRoute(route, true)
+ && !MenuRegistry.getClientRoutes(true).get(route).flowLayout();
+ }
- return statusCode;
+ /**
+ * Get the target location as a standardized route string.
+ *
+ * @param event
+ * navigation event
+ * @return route string
+ */
+ private static String getFormattedRoute(NavigationEvent event) {
+ return event.getLocation().getPath().isEmpty()
+ ? event.getLocation().getPath()
+ : event.getLocation().getPath().startsWith("/")
+ ? event.getLocation().getPath()
+ : "/" + event.getLocation().getPath();
}
/**
@@ -315,8 +399,7 @@ protected List> getTargetParentLayouts(
}
private void pushHistoryStateIfNeeded(NavigationEvent event, UI ui) {
- if (event instanceof ErrorNavigationEvent) {
- ErrorNavigationEvent errorEvent = (ErrorNavigationEvent) event;
+ if (event instanceof ErrorNavigationEvent errorEvent) {
if (isRouterLinkNotFoundNavigationError(errorEvent)) {
// #8544
event.getState().ifPresent(s -> ui.getPage().executeJs(
@@ -397,7 +480,11 @@ protected abstract void notifyNavigationTarget(Component componentInstance,
protected abstract List> getRouterLayoutTypes(
Class extends Component> routeTargetType, Router router);
- // The last element in the returned list is always a Component class
+ /**
+ * Collect the element types chain for the current navigation state.
+ *
+ * @return types chain for navigation target
+ */
private List> getTypesChain() {
final Class extends Component> routeTargetType = navigationState
.getNavigationTarget();
@@ -406,10 +493,10 @@ private List> getTypesChain() {
this.routeLayoutTypes);
Collections.reverse(layoutTypes);
- final ArrayList> chain = new ArrayList<>();
- for (Class extends RouterLayout> parentType : layoutTypes) {
- chain.add(parentType);
- }
+ final ArrayList> chain = new ArrayList<>(
+ layoutTypes);
+
+ // The last element in the returned list is always a Component class
chain.add(routeTargetType);
return chain;
}
@@ -496,7 +583,7 @@ private Deque getBeforeLeaveHandlers(UI ui) {
/**
* Inform any {@link BeforeEnterObserver}s in attaching element chain. The
* event is sent first to the {@link BeforeEnterHandler}s registered within
- * the {@link UI}, then to any element in the chain and to any of it's child
+ * the {@link UI}, then to any element in the chain and to any of its child
* components in the hierarchy which implements {@link BeforeEnterHandler}
*
* If the chain
argument is empty chainClasses
is
@@ -510,7 +597,7 @@ private Deque getBeforeLeaveHandlers(UI ui) {
* @param chain
* the chain of {@link HasElement} instances which will be
* rendered. In case this is empty it'll be populated with
- * instances according with the navigation event's location.
+ * instances according to the navigation event's location.
* @return result of observer events
*/
private Optional createChainIfEmptyAndExecuteBeforeEnterNavigation(
@@ -614,7 +701,7 @@ private Optional sendBeforeEnterEvent(
if (chain != null) {
// Reverse the chain to the stored ordered, since that is different
- // than the notification order, and also to keep
+ // from the notification order, and also to keep
// LocationChangeEvent.getRouteTargetChain backward compatible.
chain = new ArrayList<>(chain);
Collections.reverse(chain);
@@ -692,8 +779,7 @@ private Optional notifyNavigationTarget(NavigationEvent event,
*/
private static boolean isComponentElementEqualsOrChild(
BeforeEnterHandler eventHandler, Component component) {
- if (eventHandler instanceof HasElement) {
- HasElement hasElement = (HasElement) eventHandler;
+ if (eventHandler instanceof HasElement hasElement) {
final Element componentElement = component.getElement();
@@ -850,11 +936,11 @@ private NavigationEvent getNavigationEvent(NavigationEvent event,
/**
* Checks if there exists a cached component chain of the route location in
* the current window.
- *
+ *
* If retrieving the window name requires another round-trip, schedule it
* and make a new call to the handle {@link #handle(NavigationEvent)} in the
* callback. In this case, this method returns {@link Optional#empty()}.
- *
+ *
* If the chain is missing and needs to be created this method returns an
* {@link Optional} wrapping an empty {@link ArrayList}.
*/