userMenu 用户菜单

userMenu 组件显示一个按钮,当点击时,打开一个下拉信息菜单。

XML 元素

userMenu

Java 类

UserMenu

XML 属性

id - alignSelf - classNames - colspan - css - enabled - focusShortcut - openOnHover - overlayClass - tabIndex - themeNames - title - visible

事件和处理器

AttachEvent - BlurEvent - DetachEvent - FocusEvent - UserChangedEvent - buttonRenderer - headerRenderer

XML 内部元素

actionItem - componentItem - separator - textItem - viewItem

基本用法

主按钮的布局中显示了当前用户的信息。 用户点击此按钮可打开下拉菜单。

下拉菜单包含一系列操作,每个操作由一个可点击的菜单项或子菜单表示。

user menu basic

以下是一个 userMenu 的示例,使用了默认按钮渲染器和多个菜单项:

<userMenu>
    <items>
        <viewItem id="profileMenuItem" text="msg://profileMenuItem.text" icon="USER"
                  viewId="ProfileView"
                  themeNames="non-checkable"/>
        <viewItem id="settingsMenuItem" text="msg://settingsMenuItem.text" icon="COG"
                  viewClass="com.company.onboarding.view.settings.SettingsView"
                  themeNames="non-checkable"/>
        <actionItem id="themeMenuItem"
                    themeNames="non-checkable">
            <action id="themeMenuItem" type="userMenu_themeSwitch"/>
        </actionItem>
        <separator/>
        <actionItem id="logoutMenuItem" ref="logoutAction"
                    themeNames="non-checkable"/>
    </items>
</userMenu>

XML 内部元素

在 XML 中,userMenu 可以包含内部元素:

actionItem

actionItem 元素用于关联用户菜单项与点击该项时的特定 操作

可以声明式地定义一个操作,也可以使用 ref 属性指向已定义的 actionid

<actions>
    <action id="logoutAction" text="msg:///actions.logout.text" type="logout"/>
</actions>
<layout>
    <userMenu id="userMenuActions">
        <items>
            <actionItem id="aboutMenuItem">
                <action id="aboutAction" text="msg://aboutAction.text" icon="INFO_CIRCLE_O"/> (1)
            </actionItem>
            <actionItem id="logoutMenuItem" ref="logoutAction"/> (2)
        </items>
    </userMenu>
</layout>
1 声明式定义的操作。
2 引用已有操作。

当用户点击下拉菜单中的 actionItem 时,Jmix 会自动触发该 action。 可以为某个操作生成 ActionPerformedEvent 处理方法来实现其逻辑:

@Autowired
private Notifications notifications;

@Subscribe("userMenuActions.aboutMenuItem.aboutAction")
public void onUserMenuActionsAboutMenuItemAboutAction(final ActionPerformedEvent event) {
    notifications.show("About");
}

框架提供了一些内置的 用户菜单操作

componentItem

componentItem 元素可以为 userMenu 添加自定义的内部组件。

<userMenu id="userMenuComponent">
    <items>
        <componentItem id="emailItMenuItem">
            <hbox padding="false">
                <icon icon="MAILBOX"/>
                <span text="E Mail"/>
            </hbox>
        </componentItem>
    </items>
</userMenu>

在 Jmix Studio 中,可以为 componentItem 添加一个 UserMenuItem.HasClickListener.ClickEvent 事件处理的桩代码:

@Autowired
private Notifications notifications;

@Subscribe("userMenuComponent.emailItMenuItem")
public void onUserMenuComponentEmailItMenuItemClick(final UserMenuItem.HasClickListener.ClickEvent<ComponentUserMenuItem> event) {
    notifications.show("Email: test@river.net");
}

textItem

textItem 元素可以添加文本和图标。

<userMenu id="userMenuText">
    <items>
        <textItem id="contactUsMenuItem" text="msg://contactUsItem.text" icon="PHONE"/>
    </items>
</userMenu>

在 Jmix Studio 中,可以为 textItem 添加一个 UserMenuItem.HasClickListener.ClickEvent 事件处理的桩代码:

@Autowired
private Notifications notifications;

@Subscribe("userMenuText.contactUsMenuItem")
public void onUserMenuTextContactUsMenuItemClick(final UserMenuItem.HasClickListener.ClickEvent<TextUserMenuItem> event) {
    notifications.show("Phone number: +6(876)5463");
}

viewItem

viewItem 元素支持打开特定的视图。

<userMenu id="userMenuView">
    <items>
        <viewItem id="profileMenuItem" text="msg://profileMenuItem.text" icon="USER"
                  viewId="ProfileView"/> (1)
        <viewItem id="settingsMenuItem" text="msg://settingsMenuItem.text" icon="COG"
                  viewClass="com.company.onboarding.view.settings.SettingsView"
                  openMode="DIALOG"/> (2)
    </items>
</userMenu>
1 用指定的 id 打开特定视图。
2 DIALOG 模式打开指定类的视图。

separator

separator 元素用于在下拉菜单中分隔菜单项。

分隔符在满足以下条件时会自动隐藏:

  1. 多个分隔符连续出现。此时仅保留一个分隔符可见。

  2. 分隔符是第一个子元素且未定义 header 渲染器。

  3. 分隔符是最后一个子元素。

子菜单

textItemcomponentItem 元素支持定义内部菜单项,从而可以定义具有层级结构的菜单。

user menu nested
<userMenu>
    <items>
        <textItem id="helpMenuItem"
                  text="msg://helpMenuItem.text" icon="QUESTION_CIRCLE"
                  themeNames="non-checkable">
            <items>
                <textItem id="documentationMenuItem" text="msg://documentationMenuItem.text"/>
                <textItem id="aboutMenuItem" text="msg://aboutMenuItem.text"/>
            </items>
        </textItem>
    </items>
</userMenu>

样式版本

themeNames 支持为组件设置一个预定义的样式版本。

  • tertiary - 移除按钮背景

    user menu theme variant tertiary
    <userMenu>
        <items>
            <actionItem id="logoutMenuItem" ref="logoutAction"/>
        </items>
    </userMenu>
    
    <userMenu themeNames="tertiary">
        <items>
            <actionItem id="logoutMenuItem" ref="logoutAction"/>
        </items>
    </userMenu>
  • non-checkable - 移除显示对勾的区域

    user menu theme variant non checkable
    <userMenu>
        <items>
            <viewItem id="profileMenuItem" text="msg://profileMenuItem.text" icon="USER"
                      viewId="ProfileView"/>
            <viewItem id="settingsMenuItem" text="msg://settingsMenuItem.text" icon="COG"
                      viewClass="com.company.onboarding.view.settings.SettingsView"/>
            <actionItem id="themeMenuItem">
                <action id="themeMenuItem" type="userMenu_themeSwitch"/>
            </actionItem>
            <separator/>
            <actionItem id="logoutMenuItem" ref="logoutAction"/>
        </items>
    </userMenu>
    
    <userMenu>
        <items>
            <viewItem id="profileMenuItem" text="msg://profileMenuItem.text" icon="USER"
                      viewId="ProfileView"
                      themeNames="non-checkable"/>
            <viewItem id="settingsMenuItem" text="msg://settingsMenuItem.text" icon="COG"
                      viewClass="com.company.onboarding.view.settings.SettingsView"
                      themeNames="non-checkable"/>
            <actionItem id="themeMenuItem"
                        themeNames="non-checkable">
                <action id="themeMenuItem" type="userMenu_themeSwitch"/>
            </actionItem>
            <separator/>
            <actionItem id="logoutMenuItem" ref="logoutAction"
                        themeNames="non-checkable"/>
        </items>
    </userMenu>

non-checkable 样式可以在 userMenu 和子菜单级别单独设置。

如果子菜单有可以勾选的菜单,最好在一级菜单中单独定义 non-checkable 样式。

渲染器

框架为按钮和可选的菜单 header 内容的自定义功能提供饿了灵活的方案。

按钮渲染器

可以通过 setButtonRenderer() 方法或 @Install 注解设置按钮渲染器。

user menu button renderer
@Autowired
private UiComponents uiComponents;
@Autowired
private FileStorage fileStorage;

@Install(to = "userMenu", subject = "buttonRenderer")
private Component userMenuButtonRenderer(final UserDetails userDetails) {
    if (!(userDetails instanceof User user)) {
        return null;
    }

    String userName = generateUserName(user);
    Avatar avatar = createAvatar(userName, user.getPicture());
    Span name = uiComponents.create(Span.class);
    name.setText(userName);
    name.addClassName(LumoUtility.TextColor.BODY);

    HorizontalLayout content = uiComponents.create(HorizontalLayout.class);
    content.setAlignItems(FlexComponent.Alignment.CENTER);
    content.add(avatar, name);
    content.addClassNames( (1)
            LumoUtility.Padding.Horizontal.MEDIUM,
            LumoUtility.Padding.Vertical.SMALL);

    return content;
}

private String generateUserName(User user) {
    String userName = String.format("%s %s",
                    Strings.nullToEmpty(user.getFirstName()),
                    Strings.nullToEmpty(user.getLastName()))
            .trim();

    return userName.isEmpty() ? user.getUsername() : userName;
}

private Avatar createAvatar(String fullName, @Nullable FileRef fileRef) {
    Avatar avatar = uiComponents.create(Avatar.class);
    avatar.setName(fullName);
    avatar.getElement().setAttribute("tabindex", "-1"); (2)

    if (fileRef != null) {
        StreamResource streamResource = new StreamResource(
                fileRef.getFileName(),
                () -> fileStorage.openStream(fileRef));
        avatar.setImageResource(streamResource); (3)
    }

    return avatar;
}
1 默认 HorizontalLayout 的内边距太大,我们通过样式添加内边距。
2 avatar 默认是可聚焦的,但没有提供禁用聚焦的 Java API。移除 tab 索引,只有 userMenu 组件可以接收焦点。
3 avatar 通过存储在 User 实体的 picture 属性中的引用,从给定的 StreamResource 获取内容。

Header 渲染器

Header 渲染器用于定义菜单顶部的信息。可以通过 setHeaderRenderer() 方法或 @Install 注解设置 header 渲染器。

user menu header renderer
@Autowired
private UiComponents uiComponents;
@Autowired
private FileStorage fileStorage;

@Install(to = "userMenu", subject = "headerRenderer")
private Component userMenuHeaderRenderer(final UserDetails userDetails) {
    if (!(userDetails instanceof User user)) {
        return null;
    }

    String name = generateUserName(user);

    Avatar avatar = createAvatar(name, user.getPicture());
    avatar.addThemeVariants(AvatarVariant.LUMO_LARGE);
    avatar.addClassName("user-menu-avatar");

    Span text = uiComponents.create(Span.class);
    text.setText(name);
    text.setClassName("user-menu-text");

    Div content = uiComponents.create(Div.class);
    content.setClassName("user-menu-header-content"); (1)
    content.add(avatar, text);

    if (name.equals(user.getUsername())) {
        text.addClassNames("user-menu-text-subtext");
    } else {
        Span subtext = uiComponents.create(Span.class);
        subtext.setText(user.getUsername());
        subtext.setClassName("user-menu-subtext");

        content.add(subtext);
    }

    return content;
}

private String generateUserName(User user) {
    String userName = String.format("%s %s",
                    Strings.nullToEmpty(user.getFirstName()),
                    Strings.nullToEmpty(user.getLastName()))
            .trim();

    return userName.isEmpty() ? user.getUsername() : userName;
}

private Avatar createAvatar(String fullName, @Nullable FileRef fileRef) {
    Avatar avatar = uiComponents.create(Avatar.class);
    avatar.setName(fullName);
    avatar.getElement().setAttribute("tabindex", "-1"); (2)

    if (fileRef != null) {
        StreamResource streamResource = new StreamResource(
                fileRef.getFileName(),
                () -> fileStorage.openStream(fileRef));
        avatar.setImageResource(streamResource); (3)
    }

    return avatar;
}
1 组件位置通过样式定义。
2 avatar 默认是可聚焦的,但没有提供禁用聚焦的 Java API。移除 tab 索引,只有 userMenu 组件可以接收焦点。
3 avatar 通过存储在 User 实体的 picture 属性中的引用,从给定的 StreamResource 获取其内容。
.user-menu-header-content {
    display: grid;
    grid-template: "avatar text"
                   "avatar subtext";
    grid-template-columns: auto 1fr;
    column-gap: var(--lumo-space-s);

    width: 100%;
    box-sizing: border-box;

    color: var(--lumo-body-text-color);
    padding: var(--lumo-space-xs) var(--lumo-space-l) var(--lumo-space-xs) var(--lumo-space-s);
}

.user-menu-header-content > .user-menu-avatar {
    grid-area: avatar;
    align-self: center;
}

.user-menu-header-content > .user-menu-text {
    grid-area: text;

    color: var(--lumo-body-text-color);
    font-weight: 700;
    font-size: var(--lumo-font-size-m);
}

.user-menu-header-content > .user-menu-text-subtext {
    grid-row: text / subtext;
}

.user-menu-header-content > .user-menu-text {
    align-self: center;
    text-align: start;

    width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
}

.user-menu-header-content > .user-menu-subtext {
    grid-area: subtext;
    align-self: center;
    text-align: start;

    color: var(--lumo-secondary-text-color);
    font-size: var(--lumo-font-size-xs);

    width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
}

XML 属性

通用属性 对所有组件都是一样的配置。

下面是 userMenu 的特殊属性:

名称

描述

默认值

openOnHover

如果 openOnHover = true,则当该组件被鼠标或触摸聚焦时,会自动打开下拉列表。

false

事件和处理器

通用事件和处理器 对所有组件都是一样的配置。 下面是 userMenu 的特殊事件和处理器:

在 Jmix Studio 生成处理器桩代码时,可以使用 Jmix UI 组件面板的 Handlers 标签页或者视图类顶部面板的 Generate Handler 添加,也可以通过 CodeGenerate 菜单(Alt+Insert / Cmd+N)生成。

名称

描述

UserChangedEvent

io.jmix.flowui.kit.component.usermenu.JmixUserMenu.UserChangedEvent 当切换用户时发出。

buttonRenderer

设置基于当前用户信息渲染按钮的 Renderer。参阅 按钮渲染器

headerRenderer

设置基于当前用户信息渲染 header 的 Renderer 参阅 Header 渲染器