userMenu

userMenu отображает текущего пользователя и открывает меню действий, связанных с пользователем.

XML-элемент

userMenu

Java-класс

UserMenu

Основы

Основная кнопка отображает компоновку, представляющую текущего пользователя. Пользователь нажимает эту кнопку, чтобы открыть выпадающее меню.

Выпадающее меню содержит список действий, каждое из которых представлено кликабельным элементом или подменю.

Вот пример определения 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>

Рендереры

Фреймворк позволяет настраивать содержимое кнопки и необязательного заголовка меню.

Рендерер кнопки

Вы можете установить средство рендеринга кнопок либо с помощью метода setButtonRenderer(), либо с помощью аннотации @Install.

import com.company.onboarding.entity.User;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.avatar.Avatar;
import com.vaadin.flow.component.avatar.AvatarVariant;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.server.streams.DownloadHandler;
import com.vaadin.flow.server.streams.DownloadResponse;
import io.jmix.core.FileRef;
import io.jmix.core.FileStorage;
import io.jmix.flowui.UiComponents;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;

// class declaration and annotations omitted

    @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); (2)
        avatar.setName(fullName); (3)
        avatar.getElement().setAttribute("tabindex", "-1"); (4)

        if (fileRef != null) {
            avatar.setImageHandler( (5)
                    DownloadHandler.fromInputStream(event ->
                            new DownloadResponse(
                                    fileStorage.openStream(fileRef),
                                    fileRef.getFileName(),
                                    fileRef.getContentType(),
                                    -1
                            )
                    )
            );
        }

        return avatar;
    }
1 Внутренние отступы HorizontalLayout по умолчанию слишком велики, поэтому задаём нужные отступы с помощью стилей.
2 Экземпляр компонента Avatar создаётся с помощью фабрики UiComponents.
3 Полное имя пользователя передаётся в Avatar, чтобы при отсутствии изображения компонент сформировал инициалы.
4 avatar по умолчанию может получать фокус, но Java API не позволяет это отключить. Удаляем атрибут tabindex, чтобы фокус мог получать только компонент userMenu.
5 DownloadHandler передаёт изображение аватара из файлового хранилища по ссылке, сохранённой в атрибуте picture сущности User.

Рендерер заголовка

Средство визуализации заголовка определяет содержимое, отображаемое в верхней части меню. Вы можете установить средство визуализации заголовка, используя метод setHeaderRenderer() или аннотацию @Install.

import com.company.onboarding.entity.User;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.avatar.Avatar;
import com.vaadin.flow.component.avatar.AvatarVariant;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.server.streams.DownloadHandler;
import com.vaadin.flow.server.streams.DownloadResponse;
import io.jmix.core.FileRef;
import io.jmix.core.FileStorage;
import io.jmix.flowui.UiComponents;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;

// class declaration and annotations omitted

    @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); (2)
        avatar.setName(fullName); (3)
        avatar.getElement().setAttribute("tabindex", "-1"); (4)

        if (fileRef != null) {
            avatar.setImageHandler( (5)
                    DownloadHandler.fromInputStream(event ->
                            new DownloadResponse(
                                    fileStorage.openStream(fileRef),
                                    fileRef.getFileName(),
                                    fileRef.getContentType(),
                                    -1
                            )
                    )
            );
        }

        return avatar;
    }
1 Положение компонентов задаётся с помощью стилей.
2 Экземпляр компонента Avatar создаётся с помощью фабрики UiComponents.
3 Полное имя пользователя передаётся в Avatar, чтобы при отсутствии изображения компонент сформировал инициалы.
4 avatar по умолчанию может получать фокус, но Java API не позволяет это отключить. Удаляем атрибут tabindex, чтобы фокус мог получать только компонент userMenu.
5 DownloadHandler передаёт изображение аватара из файлового хранилища по ссылке, сохранённой в атрибуте picture сущности User.
.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;
}

Варианты темы

Используйте атрибут themeNames, чтобы применить один или несколько вариантов темы.

Вариант Описание Поддерживается в

tertiary

Удаляет фон с главной кнопки.

Aura, Lumo

non-checkable

Удаляет область, используемую для отображения флажка.

Aura, Lumo

Тему non-checkable можно применить к компоненту userMenu или к отдельным пунктам меню.

Если подменю содержит отмечаемые пункты, примените non-checkable отдельно к каждому пункту первого уровня.

Атрибуты

userMenu имеет следующие уникальные атрибуты:

Имя Описание По умолчанию

openOnHover

Если атрибут openOnHover равен true, раскрывающийся список элементов открывается автоматически при наведении фокуса на поле с помощью мыши или касания.

false

overlayClass

Добавляет имена CSS-классов во всплывающий элемент компонента.

userMenu имеет следующие общие атрибуты:

Обработчики

userMenu имеет следующие уникальные обработчики:

Имя Описание

UserChangedEvent

io.jmix.flowui.kit.component.usermenu.JmixUserMenu.UserChangedEvent запускается при изменении пользователя, связанного с компонентом пользовательского меню.

buttonRenderer

Устанавливает Renderer, отвечающий за отображение содержимого кнопки на основе текущего пользователя. См. Рендерер кнопок.

headerRenderer

Задаёт Renderer, который формирует содержимое заголовка для текущего пользователя. См. Рендерер заголовка.

userMenu имеет следующие общие обработчики:

Элементы

userMenu, определённый в XML-дескрипторе, может содержать вложенные элементы:

actionItem

Элемент actionItem создаёт связь между пунктом пользовательского меню и конкретным действие, который должен быть выполнен при щелчке по этому пункту.

Вы можете либо определить действие декларативно, либо использовать атрибут ref, чтобы сослаться на id уже определённого action.

<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");
}

Фреймворк предоставляет предопределённые User Menu Actions.

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 можно сгенерировать заготовку обработчика UserMenuItem.HasClickListener.ClickEvent для componentItem.

@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 можно сгенерировать заготовку обработчика UserMenuItem.HasClickListener.ClickEvent для textItem.

@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 Открывает экран с указанным идентификатором.
2 Открывает экран указанного класса в режиме DIALOG.

separator

Элемент separator используется для визуального разделения элементов в выпадающем меню.

Разделители автоматически скрываются при следующих условиях:

  1. Несколько разделителей идут подряд. В этом случае видимым остается только один разделитель.

  2. Разделитель является первым дочерним элементом, а рендерер заголовка не определен.

  3. Разделитель является последним дочерним элементом.

Вложенные элементы

Элементы textItem и componentItem поддерживают вложенные элементы, что позволяет определить иерархическое меню.

<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>