Собственные типы действий
В проекте можно создать собственные типы действий или переопределить существующие стандартные типы.
Предположим, что вы хотите создать действие, которое бы показывало имя экземпляра текущей сущности, выбранной в таблице, и использовать это действие в различных экранах, указывая только его тип. Чтобы это сделать, необходимо выполнить следующие шаги:
-
Создайте для действия отдельный класс с аннотацией
@ActionType
, в которой укажите желаемое имя типа:@ActionType("showSelected") public class ShowSelectedAction extends ItemTrackingAction { @Autowired private MetadataTools metadataTools; private String description; public ShowSelectedAction(String id) { super(id); setCaption("Show Selected"); } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } @Override public void actionPerform(Component component) { if (getTarget() != null) { Object selected = getTarget().getSingleSelected(); if (selected != null) { Notifications notifications = ComponentsHelper.getScreenContext(target).getNotifications(); notifications.create() .withType(Notifications.NotificationType.TRAY) .withDescription(description) .withCaption(metadataTools.getInstanceName(selected)) .show(); } } } }
-
Теперь вы можете использовать действие в дескрипторах экрана, просто указывая его тип:
<groupTable id="custTable" multiselect="true" width="100%" dataContainer="customersDc"> <actions> <action id="show" type="showSelected"> <properties> <property name="description" value="Information about the selected item"/> </properties> </action> </actions> <columns> <column id="firstName"/> <column id="lastName"/> <column id="rewardPoints"/> <column id="level"/> </columns> <buttonsPanel alwaysVisible="true"> <button action="custTable.show"/> </buttonsPanel> </groupTable>
Если вы хотите переопределить существующий тип действия, просто зарегистрируйте свое новое действие с таким же типом. |
Использование собственных действий в Studio
Собственные типы действий, реализованные в вашем проекте, могут быть встроены в интерфейс дизайнера экранов Jmix Studio. Дизайнер экранов предоставляет следующую поддержку для собственных типов действий:
-
Возможность выбрать тип действия в списке стандартных действий во время добавления нового действия в экран из палитры или при выполнении +Add → Action в таблице.
-
Быстрая навигация из места использования действия к классу действия по нажатию Ctrl + B или Ctrl-клику мыши, когда курсор находится на типе действия в дескрипторе экрана (например на атрибуте
showSelected
в XML-фрагменте<action id="show" type="showSelected">
). -
Редактирование определенных пользователем свойств действия в панели Component Inspector.
-
Генерация обработчиков событий и делегирующих методов, объявленных в классе действия для кастомизации его логики.
-
Поддержка параметризации генерик-типом. Генерик-тип определяется как класс сущности, используемый в таблице (компонент-владелец действия).
Аннотация @io.jmix.ui.meta.StudioAction
используется для пометки собственного класса действия, содержащего дополнительные свойства, определенные пользователем. Собственные действия нужно помечать этой аннотацией, однако в данный момент Studio не использует дополнительных атрибутов аннотации @StudioAction
.
Аннотация @io.jmix.ui.meta.StudioPropertiesItem
используется, чтобы пометить setter-метод свойства действия как редактируемое свойство. Такие свойства будут отображаться и редактироваться в панели Component Inspector дизайнера экранов. Аннотация имеет следующие атрибуты:
-
name
- название атрибута, который будет сохраняться в XML. Если не установлено, то название будет определено по именованию setter-метода. -
type
- тип свойства. Используется панелью Component Inspector, чтобы создать подходящий компонент ввода, предоставляющий подсказки и базовую валидацию. -
caption
- подпись свойства, отображается в панели Component Inspector. -
description
- дополнительное описание свойства, отображается в панели Component Inspector как всплывающая подсказка по наведению мыши. -
category
- категория свойства в панели Component Inspector (на данный момент еще не используется дизайнером экранов). -
required
- обязательность свойства. Если свойство обязательное, то панель Component Inspector не позволит ввести для него пустое значение. -
defaultValue
- значение свойства по умолчанию, т.е. значение, которое неявно используется действием, если соответствующий XML-атрибут отсутствует. Значение по умолчанию не будет записываться в XML. -
options
- список вариантов для свойства действия, например для типа свойстваENUMERATION
.
Заметьте, что для свойств действий поддерживается только ограниченный набор Java типов:
-
Примитивные типы
String
,Boolean
,Byte
,Short
,Integer
,Long
,Float
,Double
. -
Перечисления.
-
java.lang.Class
. -
java.util.List
– список, состоящий из элементов, чьи типы указаны выше. Панель Component Inspector не имеет точно подходящего компонента для этого Java класса, поэтому этот тип должен вводиться как обычная строка, и помечаться какPropertyType.STRING
.
Примеры:
private String contentType = "PLAIN";
private Class<? extends Screen> dialogClass;
private List<Integer> columnNumbers = new ArrayList<>();
@StudioPropertiesItem(name = "ctype", type = PropertyType.ENUMERATION, description = "Email content type", (1)
defaultValue = "PLAIN", options = {"PLAIN", "HTML"}
)
public void setContentType(String contentType) {
this.contentType = contentType;
}
@StudioPropertiesItem(type = PropertyType.SCREEN_CLASS_NAME, required = true) (2)
public void setDialogClass(Class<? extends Screen> dialogClass) {
this.dialogClass = dialogClass;
}
@StudioPropertiesItem(type = PropertyType.STRING) (3)
public void setColumnNumbers(List<Integer> columnNumbers) {
this.columnNumbers = columnNumbers;
}
1 | Строковое свойство с ограниченным набором вариантов ввода и значением по умолчанию. |
2 | Обязательное свойство с набором вариантов - списком классов экранов, определенных в проекте. |
3 | Список целых чисел. Тип свойства установлен как STRING , т.к. панель Component Inspector не содержит подходящего компонента ввода. |
Также Studio предоставляет поддержку для событий и делегирующих методов в собственных действиях – такую же, как и для встроенных UI компонентов. Для объявления слушателя события или делегирующего метода в классе действия не требуется никаких дополнительных аннотаций. Пример их объявления приведен ниже.
Пример: SendByEmailAction
Этот пример демонстрирует:
-
объявление и аннотирование класса собственного действия.
-
аннотирование редактируемых параметров действия.
-
объявление собственного класса события, производимого действием, и его обработчика.
-
объявление делегирующих методов.
Действие SendByEmailAction
реализует посылку email о сущности, выбранной в таблице, которой принадлежит действие. Это действие реализовано как полностью конфигурируемое, большая часть его внутренней логики может быть изменена с помощью редактируемых свойств, делегирующих методов и событий.
Исходный код действия:
@StudioAction(target = "io.jmix.ui.component.ListComponent", description = "Sends selected entity by email")(1)
@ActionType("sendByEmail")(2)
public class SendByEmailAction <E> extends ItemTrackingAction {(3)
@Autowired
private MetadataTools metadataTools;
@Autowired
private Emailer emailer;
private String recipientAddress = "admin@example.com";
private Function<E, String> bodyGenerator;
private Function<E, List<EmailAttachment>> attachmentProvider;
public SendByEmailAction(String id) {
super(id);
setCaption("Send by email");
}
@StudioPropertiesItem(required = true, defaultValue = "admin@example.com")(4)
public void setRecipientAddress(String recipientAddress) {
this.recipientAddress = recipientAddress;
}
public Subscription addEmailSentListener(Consumer<EmailSentEvent> listener) {(5)
return getEventHub().subscribe(EmailSentEvent.class, listener);
}
public void setBodyGenerator(Function<E, String> bodyGenerator) {(6)
this.bodyGenerator = bodyGenerator;
}
public void setAttachmentProvider(Function<E, List<EmailAttachment>> attachmentProvider) {(7)
this.attachmentProvider = attachmentProvider;
}
@Override
public void actionPerform(Component component) {
if (recipientAddress == null || bodyGenerator == null) {
throw new IllegalStateException("Required parameters are not set");
}
E selected = (E) getTarget().getSingleSelected();
if (selected == null) {
return;
}
String caption = "Entity " + metadataTools.getInstanceName(selected) + " info";
String body = bodyGenerator.apply(selected);(8)
List<EmailAttachment> attachments = attachmentProvider != null ? attachmentProvider.apply(selected)(9)
: new ArrayList<>();
EmailInfo info = EmailInfoBuilder.create()
.setAddresses(recipientAddress)
.setSubject(caption)
.setBody(body)
.setBodyContentType(EmailInfo.TEXT_CONTENT_TYPE)
.setAttachments(attachments.toArray(new EmailAttachment[0]))
.build();
emailer.sendEmailAsync(info);(10)
EmailSentEvent event = new EmailSentEvent(this, info);
eventHub.publish(EmailSentEvent.class, event);(11)
}
public static class EmailSentEvent extends EventObject {(12)
private final EmailInfo emailInfo;
public EmailSentEvent(SendByEmailAction origin, EmailInfo emailInfo) {
super(origin);
this.emailInfo = emailInfo;
}
public EmailInfo getEmailInfo() {
return emailInfo;
}
}
}
1 | Класс действия помечен аннотацией @StudioAction . |
2 | id действия устанавливается аннотацией @ActionType . |
3 | Класс действия параметризован типом E – это тип сущности, которая отображается компонентом таблицы. |
4 | Адрес получателя email выставлен как редактируемое свойство действия. |
5 | Метод, регистрирующий слушатель для события EmailSentEvent . Этот метод определяется Studio как объявление обработчика события в действии. |
6 | Метод, устанавливающий объект Function . Этот объект используется для делегирования (контроллеру экрана) логики составления текста письма. Этот метод определяется Studio как объявление делегирующего метода. |
7 | Объявление другого делегирующего метода – на этот раз он используется для делегирования логики создания вложений к письму. Заметьте, что оба делегирующих метода используют параметр E генерик-типа. |
8 | Обязательный делегирующий метод (реализованный в контроллере экрана) вызывается для составления текста письма. |
9 | Если установлен, необязательный делегирующий метод вызывается для генерации вложений к письму. |
10 | Здесь, собственно, и посылается email. |
11 | Событие EmailSentEvent публикуется после успешной посылки письма. Если контроллер экрана был подписан на это событие, то будет вызван соответствующий обработчик. |
12 | Объявление класса события. Обратите внимание, что в класс события можно добавить поля и таким образом передавать в логику обработки события дополнительные данные. |
Если реализовать класс как показано выше, то Studio отобразит новое собственное действие вместе со стандартными действиями в диалоге создания действия:
Когда действие добавлено в дескриптор экрана, вы можете выбрать его и редактировать его свойства в панели Component Inspector:
Когда свойство собственного действия изменяется в панели Component Inspector, оно записывается в дескриптор экрана следующим образом:
<action id="sendByEmail" type="sendByEmail">
<properties>
<property name="recipientAddress" value="peter@example.com"/>
</properties>
</action>
Обработчики событий и делегирующие методы действия также отображаются и доступны для генерации в панели Component Inspector:
Пример сгенерированной логики с реализованными обработчиками события и делегирующими методами выглядит следующим образом:
@UiController("table-screen")
@UiDescriptor("table-screen.xml")
public class TableScreen extends Screen {
@Autowired
private Notifications notifications;
@Named("customersTable.sendByEmail")(1)
private SendByEmailAction<Customer> customersTableSendByEmail;
@Subscribe("customersTable.sendByEmail")
public void onCustomersTableSendByEmailEmailSent(SendByEmailAction.EmailSentEvent event) {(2)
notifications.create(Notifications.NotificationType.HUMANIZED)
.withCaption("Email sent")
.show();
}
@Install(to = "customersTable.sendByEmail", subject = "bodyGenerator")
private String customersTableSendByEmailBodyGenerator(Customer customer) {(3)
return "Hello, " + customer.getFirstName();
}
@Install(to = "customersTable.sendByEmail", subject = "attachmentProvider")
private List<EmailAttachment> customersTableSendByEmailAttachmentProvider(Customer customer) {(4)
return Collections.emptyList();
}
}
1 | При инжекции действия используется корректный параметр типа. |
2 | Реализация обработчика события. |
3 | Реализация делегирующего метода bodyGenerator . Параметр типа Customer подставлен в сигнатуру метода. |
4 | Реализация делегирующего метода attachmentProvider . |