userMenu
userMenu отображает текущего пользователя и открывает меню действий, связанных с пользователем.
XML-элемент |
|
|---|---|
Java-класс |
|
Основы
Основная кнопка отображает компоновку, представляющую текущего пользователя. Пользователь нажимает эту кнопку, чтобы открыть выпадающее меню.
Выпадающее меню содержит список действий, каждое из которых представлено кликабельным элементом или подменю.
Вот пример определения 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, чтобы применить один или несколько вариантов темы.
| Вариант | Описание | Поддерживается в |
|---|---|---|
|
Удаляет фон с главной кнопки. |
Aura, Lumo |
|
Удаляет область, используемую для отображения флажка. |
Aura, Lumo |
Тему non-checkable можно применить к компоненту userMenu или к отдельным пунктам меню.
Если подменю содержит отмечаемые пункты, примените non-checkable отдельно к каждому пункту первого уровня.
|
Атрибуты
userMenu имеет следующие уникальные атрибуты:
| Имя | Описание | По умолчанию |
|---|---|---|
Если атрибут |
|
|
Добавляет имена CSS-классов во всплывающий элемент компонента. |
— |
userMenu имеет следующие общие атрибуты:
id - alignSelf - classNames - colspan - css - enabled - focusShortcut - tabIndex - themeNames - title - visible
Обработчики
userMenu имеет следующие уникальные обработчики:
| Имя | Описание |
|---|---|
|
|
Устанавливает |
|
Задаёт |
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 используется для визуального разделения элементов в выпадающем меню.
Разделители автоматически скрываются при следующих условиях:
-
Несколько разделителей идут подряд. В этом случае видимым остается только один разделитель.
-
Разделитель является первым дочерним элементом, а рендерер заголовка не определен.
-
Разделитель является последним дочерним элементом.
Вложенные элементы
Элементы 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>