3.5.5.3. Собственные типы действий
В проекте можно создать собственные типы действий или переопределить существующие стандартные типы.
Предположим, что вы хотите создать действие, которое бы показывало имя экземпляра текущей сущности, выбранной в таблице, и использовать это действие в различных экранах, указывая только его тип. Чтобы это сделать, необходимо выполнить следующие шаги:
-
Создайте для действия отдельный класс с аннотацией
@ActionType
, в которой укажите желаемое имя типа:package com.company.sample.web.actions; import com.haulmont.cuba.core.entity.Entity; import com.haulmont.cuba.core.global.MetadataTools; import com.haulmont.cuba.gui.ComponentsHelper; import com.haulmont.cuba.gui.Notifications; import com.haulmont.cuba.gui.components.ActionType; import com.haulmont.cuba.gui.components.Component; import com.haulmont.cuba.gui.components.actions.ItemTrackingAction; import javax.inject.Inject; @ActionType("showSelected") public class ShowSelectedAction extends ItemTrackingAction { @Inject private MetadataTools metadataTools; public ShowSelectedAction(String id) { super(id); setCaption("Show Selected"); } @Override public void actionPerform(Component component) { Entity selected = getTarget().getSingleSelected(); if (selected != null) { Notifications notifications = ComponentsHelper.getScreenContext(target).getNotifications(); notifications.create() .withType(Notifications.NotificationType.TRAY) .withCaption(metadataTools.getInstanceName(selected)) .show(); } } }
-
В файле
web-spring.xml
добавьте элемент<gui:actions>
, и в его атрибутеbase-packages
укажите пакет, в котором нужно искать ваши аннотированные действия:<beans ... xmlns:gui="http://schemas.haulmont.com/cuba/spring/cuba-gui.xsd"> <!-- ... --> <gui:actions base-packages="com.company.sample.web.actions"/> </beans>
-
Теперь вы можете использовать действие в дескрипторах экрана, просто указывая его тип:
<groupTable id="customersTable"> <actions> <action id="show" type="showSelected"/> </actions> <columns> <!-- ... --> </columns> <buttonsPanel> <button action="customersTable.show"/> </buttonsPanel> </groupTable>
Если вы хотите переопределить существующий тип действия, просто зарегистрируйте свое новое действие с таким же типом. |
- Поддержка в CUBA Studio и редактируемые свойства для собственных действий
Собственные типы действий, реализованные в вашем проекте, могут быть встроены в интерфейс дизайнера экранов CUBA Studio. Дизайнер экранов предоставляет следующую поддержку для собственных типов действий:
-
Возможность выбрать тип действия в списке стандартных действий во время добавления нового действия в экран из палитры или при выполнении +Add → Action в таблице.
-
Быстрая навигация из места использования действия к классу действия по нажатию Ctrl + B или Ctrl-клику мыши, когда курсор находится на типе действия в дескрипторе экрана (например на атрибуте
showSelected
в XML-фрагменте<action id="sel" type="showSelected">
). -
Редактирование определенных пользователем свойств действия в панели Component Inspector.
-
Генерация обработчиков событий и делегирующих методов, объявленных в классе действия для кастомизации его логики.
-
Поддержка параметризации генерик-типом. Генерик-тип определяется как класс сущности, используемый в таблице (компонент-владелец действия).
Аннотация @com.haulmont.cuba.gui.meta.StudioAction
используется для пометки собственного класса действия, содержащего дополнительные свойства, определенные пользователем. Собственные действия нужно помечать этой аннотацией, однако в данный момент Studio не использует дополнительных атрибутов аннотации @StudioAction
.
Аннотация @com.haulmont.cuba.gui.meta.StudioPropertiesItem
используется, чтобы пометить setter-метод свойства действия как редактируемое свойство. Такие свойства будут отображаться и редактироваться в панели Component Inspector дизайнера экранов. Аннотация имеет следующие атрибуты:
-
name
- название атрибута, который будет сохраняться в XML. Если не установлено, то название будет определено по именованию setter-метода. -
type
- тип свойства. Используется панелью Inspector, чтобы создать подходящий компонент ввода, предоставляющий подсказки и базовую валидацию. Описания всех типов свойств приведены здесь. -
caption
- подпись свойства, отображается в панели Inspector. -
description
- дополнительное описание свойства, отображается в панели Inspector как всплывающая подсказка по наведению мыши. -
category
- категория свойства в панели Inspector (на данный момент еще не используется дизайнером экранов). -
required
- обязательность свойства. Если свойство обязательное, то панель Inspector не позволит ввести для него пустое значение. -
defaultValue
- значение свойства по умолчанию, т.е. значение, которое неявно используется действием, если соответствующий XML атрибут отсутствует. Значение по умолчанию не будет записываться в XML. -
options
- список вариантов для свойства действия, например для типа свойстваENUMERATION
.
Заметьте, что для свойств действий поддерживается только ограниченный набор Java типов:
-
Примитивные типы:
String
,Boolean
,Byte
,Short
,Integer
,Long
,Float
,Double
. -
Перечисления.
-
java.lang.Class
. -
java.util.List
- список, состоящий из элементов, чьи типы указаны выше. Панель 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 , т.к. панель Inspector не содержит подходящего компонента ввода. |
Также Studio предоставляет поддержку для событий и делегирующих методов в собственных действиях - такую же, как и для встроенных UI компонентов. Для объявления слушателя события или делегирующего метода в классе действия не требуется никаких дополнительных аннотаций. Пример их объявления приведен ниже.
- Пример настраиваемого действия: SendByEmailAction
Этот пример демонстрирует:
-
объявление и аннотирование класса собственного действия.
-
аннотирование редактируемых параметров действия.
-
объявление собственного класса события, производимого действием, и его обработчика.
-
объявление делегирующих методов.
Действие SendByEmailAction
реализует посылку email о сущности, выбранной в таблице, которой принадлежит действие. Это действие реализовано как полностью конфигурируемое, большая часть его внутренней логики может быть изменена с помощью редактируемых свойств, делегирующих методов и событий.
Исходный код действия:
@StudioAction(category = "List Actions", description = "Sends selected entity by email") (1)
@ActionType("sendByEmail") (2)
public class SendByEmailAction<E extends Entity> extends ItemTrackingAction { (3)
private final MetadataTools metadataTools;
private final EmailService emailService;
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");
emailService = AppBeans.get(EmailService.NAME);
metadataTools = AppBeans.get(MetadataTools.NAME);
}
@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)
.setCaption(caption)
.setBody(body)
.setBodyContentType(EmailInfo.TEXT_CONTENT_TYPE)
.setAttachments(attachments.toArray(new EmailAttachment[0]))
.build();
emailService.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:
Когда свойство собственного действия изменяется в панели Inspector, оно записывается в дескриптор экрана следующим образом:
<action id="sendByEmail" type="sendByEmail">
<properties>
<property name="recipientAddress" value="peter@example.com"/>
</properties>
</action>
Обработчики событий и делегирующие методы действия также отображаются и доступны для генерации в панели Component Inspector:
Пример сгенерированной логики с реализованными обработчиками события и делегирующими методами выглядит следующим образом:
@UiController("sales_Customer.browse")
@UiDescriptor("customer-browse.xml")
@LookupComponent("customersTable")
@LoadDataBeforeShow
public class CustomerBrowse extends StandardLookup<Customer> {
@Inject
private Notifications notifications;
@Named("customersTable.sendByEmail")
private SendByEmailAction<Customer> customersTableSendByEmail; (1)
@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.getName();
}
@Install(to = "customersTable.sendByEmail", subject = "attachmentProvider")
private List<EmailAttachment> customersTableSendByEmailAttachmentProvider(Customer customer) { (4)
return Collections.emptyList();
}
}
1 | - при инжекции действия используется корректный параметр типа. |
2 | - реализация обработчика события. |
3 | - реализация делегирующего метода bodyGenerator . Параметр типа Customer подставлен в сигнатуру метода. |
4 | - реализация делегирующего метода attachmentProvider . |