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