userMenu
userMenu отображает основную кнопку, по нажатию на которую открывается выпадающее меню со списком элементов.
XML-элемент |
|
|---|---|
Java-класс |
|
Атрибуты |
id - alignSelf - classNames - colspan - css - enabled - focusShortcut - openOnHover - overlayClass - tabIndex - themeNames - title - visible |
Обработчики |
AttachEvent - BlurEvent - DetachEvent - FocusEvent - UserChangedEvent - buttonRenderer - headerRenderer |
Элементы |
actionItem - componentItem - separator - textItem - viewItem |
Основы
Основная кнопка отображает компоновку, представляющую текущего пользователя. Пользователь нажимает эту кнопку, чтобы открыть выпадающее меню.
Выпадающее меню содержит список действий, каждое из которых представлено кликабельным элементом или подменю.
Ниже приведен пример определения 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>
Элементы
userMenu, определенный в XML-дескрипторе, может содержать вложенные элементы:
actionItem
Элемент actionItem создает связь между пунктом пользовательского меню и конкретным action, который должен быть выполнен при щелчке по этому пункту.
Вы можете либо определить действие декларативно, либо использовать атрибут 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>
Варианты темы
Используйте атрибут themeNames для настройки темы компонента.
| Вариант | Описание | Поддерживается в |
|---|---|---|
|
Удаляет фон у основной кнопки. |
Aura, Lumo |
|
Удаляет область, используемую для отображения флажка выбора. |
Aura, Lumo |
Тему non-checkable можно применять как к компоненту userMenu, так и к отдельным пунктам меню.
Если подменю содержит элементы с флажками выбора, применяйте non-checkable отдельно к каждому элементу первого уровня.
|
Рендереры
Фреймворк позволяет гибко настраивать содержимое кнопки и необязательного заголовка меню.
Рендерер кнопки
Рендерер кнопки можно задать либо методом 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;
}
Атрибуты
В Jmix существует множество общих атрибутов, которые выполняют одну и ту же функцию для всех компонентов.
Ниже приведены атрибуты, специфичные для userMenu:
Название |
Описание |
Значение по умолчанию |
|---|---|---|
Если атрибут |
|
Обработчики
В Jmix существует множество общих обработчиков, которые настраиваются одинаково для всех компонентов.
Ниже приведены обработчики, специфичные для userMenu:
|
Чтобы сгенерировать заглушку обработчика в Jmix Studio, используйте вкладку Handlers панели инспектора Jmix UI, или команду Generate Handler, доступную на верхней панели контроллера экрана и через меню Code → Generate (Alt+Insert / Cmd+N). |
Название |
Описание |
|---|---|
Событие |
|
Устанавливает |
|
Устанавливает |