From 312b4b996273f3d90534e5ac7561a6c27c9cbea3 Mon Sep 17 00:00:00 2001 From: Mikael Grankvist Date: Mon, 23 Dec 2024 13:14:41 +0200 Subject: [PATCH] chore: refactr navigation state renderer Make the handle workflow easier to read. Add javadoc for clarification of parts that are executed. --- .../AbstractNavigationStateRenderer.java | 230 ++++++++++++------ 1 file changed, 158 insertions(+), 72 deletions(-) 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 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 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 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 routeTargetType = navigationState .getNavigationTarget(); @@ -406,10 +493,10 @@ private List> getTypesChain() { this.routeLayoutTypes); Collections.reverse(layoutTypes); - final ArrayList> chain = new ArrayList<>(); - for (Class 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}. */