Skip to content

Commit

Permalink
feat: Add a helper method for menu's page title (#20165) (#20194)
Browse files Browse the repository at this point in the history
Adds a new static helper method that gives a page title for views shown using menu.

Fixes #20158

Co-authored-by: Mikhail Shabarov <[email protected]>
  • Loading branch information
vaadin-bot and mshabarov authored Oct 9, 2024
1 parent 7ea97e1 commit 48ab83e
Show file tree
Hide file tree
Showing 5 changed files with 404 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ public class MenuRegistry {
* @return routes with view information
*/
public static Map<String, AvailableViewInfo> collectMenuItems() {
Map<String, AvailableViewInfo> menuRoutes = new MenuRegistry()
Map<String, AvailableViewInfo> menuRoutes = MenuRegistry
.getMenuItems(true);
menuRoutes.entrySet()
.removeIf(entry -> Optional.ofNullable(entry.getValue())
Expand Down Expand Up @@ -132,15 +132,14 @@ public static List<AvailableViewInfo> collectMenuItemsList(Locale locale) {
* {@code true} to filter routes by authentication status
* @return routes with view information
*/
public Map<String, AvailableViewInfo> getMenuItems(
public static Map<String, AvailableViewInfo> getMenuItems(
boolean filterClientViews) {
RouteConfiguration routeConfiguration = RouteConfiguration
.forApplicationScope();

Map<String, AvailableViewInfo> menuRoutes = new HashMap<>();

menuRoutes.putAll(collectClientMenuItems(filterClientViews,
VaadinService.getCurrent().getDeploymentConfiguration()));
Map<String, AvailableViewInfo> menuRoutes = new HashMap<>(
collectClientMenuItems(filterClientViews, VaadinService
.getCurrent().getDeploymentConfiguration()));

collectAndAddServerMenuItems(routeConfiguration, menuRoutes);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -960,11 +960,7 @@ private static void updatePageTitle(NavigationEvent navigationEvent,
routeTarget).map(PageTitle::value).orElse("");

// check for HasDynamicTitle in current router targets chain
String title = navigationEvent.getUI().getInternals()
.getActiveRouterTargetsChain().stream()
.filter(HasDynamicTitle.class::isInstance)
.map(tc -> ((HasDynamicTitle) tc).getPageTitle())
.filter(Objects::nonNull).findFirst()
String title = RouteUtil.getDynamicTitle(navigationEvent.getUI())
.orElseGet(() -> Optional
.ofNullable(
MenuRegistry.getClientRoutes(true).get(route))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import com.vaadin.flow.di.Lookup;
import com.vaadin.flow.internal.AnnotationReader;
import com.vaadin.flow.router.DefaultRoutePathProvider;
import com.vaadin.flow.router.HasDynamicTitle;
import com.vaadin.flow.router.Layout;
import com.vaadin.flow.router.ParentLayout;
import com.vaadin.flow.router.Route;
Expand Down Expand Up @@ -581,4 +582,21 @@ public static boolean isAutolayoutEnabled(Class<?> target, String path) {
&& target.getAnnotation(Route.class).layout().equals(UI.class);

}

/**
* Get optional dynamic page title from the active router targets chain of a
* given UI instance.
*
* @param ui
* instance of UI, not {@code null}
* @return dynamic page title found in the routes chain, or empty optional
* if no implementor of {@link HasDynamicTitle} was found
*/
public static Optional<String> getDynamicTitle(UI ui) {
return Objects.requireNonNull(ui).getInternals()
.getActiveRouterTargetsChain().stream()
.filter(HasDynamicTitle.class::isInstance)
.map(element -> ((HasDynamicTitle) element).getPageTitle())
.filter(Objects::nonNull).findFirst();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,19 @@

package com.vaadin.flow.server.menu;

import java.io.Serializable;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Optional;

import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.Tag;
import com.vaadin.flow.component.UI;
import com.vaadin.flow.internal.menu.MenuRegistry;
import com.vaadin.flow.router.HasDynamicTitle;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.internal.PathUtil;
import com.vaadin.flow.router.internal.RouteUtil;

/**
* Menu configuration helper class to retrieve available menu entries for
Expand Down Expand Up @@ -55,6 +63,108 @@ public static List<MenuEntry> getMenuEntries(Locale locale) {
.map(MenuConfiguration::createMenuEntry).toList();
}

/**
* Retrieves the page header of the currently shown view. Can be used in
* Flow main layouts to render a page header.
* <p>
* Attempts to retrieve header from the following sources:
* <ul>
* <li>from {@code ViewConfig.title} of the client-side views;</li>
* <li>from {@link HasDynamicTitle#getPageTitle()} if present, then from
* {@link PageTitle} value of the server-side route</li>
* </ul>
* <p>
* For server-side routes it falls back to route's Java class name, if a
* non-null {@code content} is given. For client-side views it falls back to
* the React element's function name, if a page header couldn't be retrieved
* from the {@code ViewConfig}.
* <p>
* Use {@link #getPageHeader()} method, if a content object is not
* available.
*
* @param content
* as a {@link Component} class that represents a content in
* layout, can be {@code null}, if unavailable.
* @return optional page header for layout
*/
public static Optional<String> getPageHeader(Component content) {
if (isServerSideContent(content)) {
UI ui = UI.getCurrent();
if (ui != null) {
Optional<String> maybeTitle = RouteUtil.getDynamicTitle(ui);
if (maybeTitle.isPresent()) {
return maybeTitle;
}
}

return Optional.of(MenuRegistry.getTitle(content.getClass()));
}
return getPageHeaderFromMenuItems();
}

/**
* Retrieves the page header of the currently shown view. Can be used in
* Flow main layouts to render a page header.
* <p>
* Attempts to retrieve header from the following sources:
* <ul>
* <li>from {@code ViewConfig.title} of the client-side views;</li>
* <li>from {@link HasDynamicTitle#getPageTitle()} if present, then from
* {@link PageTitle} value of the server-side route</li>
* </ul>
* <p>
* For server-side routes it falls back to route's Java class name. For
* client-side views it falls back to the React element's function name, if
* a page header couldn't be retrieved from the {@code ViewConfig}.
* <p>
* Note that the possible sources of page header are limited to only
* available views in automatic menu configuration. If a route has a
* mandatory route parameters or has a route template, then it won't be used
* as a possible header source, even if it's shown.
* <p>
* Use {@link #getPageHeader(Component)} if content object is available,
* e.g. in {@link com.vaadin.flow.router.RouterLayout} based layouts.
*
* @return optional page header for layout
*/
public static Optional<String> getPageHeader() {
return getPageHeader(null);
}

private static boolean isServerSideContent(Component content) {
if (content == null) {
return false;
} else {
Tag tag = content.getClass().getAnnotation(Tag.class);
// client-side view if it is wrapped into ReactRouterOutlet
return tag == null || !"react-router-outlet".equals(tag.value());
}
}

private static Optional<String> getPageHeaderFromMenuItems() {
UI ui = UI.getCurrent();
if (ui != null) {
// Flow main layout + client views case:
// layout may have dynamic title
Optional<String> maybeTitle = RouteUtil.getDynamicTitle(ui);
if (maybeTitle.isPresent()) {
return maybeTitle;
}

String activeLocation = PathUtil.trimPath(
ui.getInternals().getActiveViewLocation().getPath());

List<AvailableViewInfo> menuItems = MenuRegistry.getMenuItems(false)
.values().stream().toList();

return menuItems.stream()
.filter(menuItem -> PathUtil.trimPath(menuItem.route())
.equals(activeLocation))
.map(AvailableViewInfo::title).findFirst();
}
return Optional.empty();
}

private static MenuEntry createMenuEntry(AvailableViewInfo viewInfo) {
if (viewInfo.menu() == null) {
return new MenuEntry(viewInfo.route(), viewInfo.title(), null,
Expand Down
Loading

0 comments on commit 48ab83e

Please sign in to comment.