自定义操作类型

在项目中可以创建自己的操作类型或者重载已有的标准类型。

例如,假设需要一个操作显示表格中当前选中实体的实例名称。你还希望该操作能在多个界面仅通过指定操作的类型便可使用。按照下面的步骤创建这种操作:

  1. 创建一个操作类添加 @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();
                }
            }
        }
    }
  2. 现在可以在界面描述中指定操作类型使用该操作:

    <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 的界面设计器中。界面设计器为自定义操作类型提供下列支持:

  • 支持在标准操作的列表中选择自定义操作,可以从工具箱(palette)为界面添加,或者为表格通过 +AddAction 方式添加。

  • 支持从操作的使用处切换至操作类定义处的快速代码跳转。在界面 xml 描述中,当光标在 action type 时,通过按下 Ctrl + B 或者按下 ctrl 用鼠标点击类型名时,会自动跳转到操作类定义代码。比如,在这段 xml <action id="sel" type="showSelected"> 中,可以点击 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 类型:

  • 基础类型:StringBooleanByteShortIntegerLongFloatDouble

  • 枚举。

  • java.lang.Class

  • 以上类型的 java.util.List。这种类型在 Component Inspector 面板没有特定的输入组件,所以需要用字符串形式输入并标记为 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 @ActionType 设置操作的 id。
3 操作类有 E 泛型参数 - 表示在所属表格中存储的实体类型。
4 Email 收件人地址通过操作属性设置。
5 EmailSentEvent 事件添加监听器的方法。Studio 检测出该方法作为操作的事件处理器。
6 设置代理 Function 对象的方法,将生成邮件体的逻辑代理给界面控制器。Studio 检测该方法作为操作的代理方法。
7 声明另一代理方法,例子中是将创建附件的逻辑代理出去。注意,这里两个代理方法都用了 E 泛型参数。
8 在界面控制器必须实现的代理方法,生成邮件体。
9 如果设置了的话,调用可选的代理方法创建附件。
10 真正发送邮件的地方。
11 邮件发送成功后发布 EmailSentEvent 事件,如果界面控制器订阅了该事件,则会调用对应的事件处理器。
12 声明事件类,注意,这里可以为事件类添加更多字段,将有用的信息传递给事件处理逻辑。

用上面示例的方式完成代码后,Studio 会在创建操作的界面与标准操作一同显示新的自定义操作:

studio custom action

为界面描述添加操作之后,可以在 Component Inspector 面板选择并修改其属性:

studio custom action properties

自定义操作的属性在 Component Inspector 修改后,会用下面的格式写入界面描述文件:

<action id="sendByEmail" type="sendByEmail">
    <properties>
        <property name="recipientAddress" value="peter@example.com"/>
    </properties>
</action>

Component Inspector 面板也同样会显示操作的事件处理器和代理方法,用来生成相应代码:

studio custom action handlers

在界面控制器使用生成代理方法和事件处理器代码的示例:

@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 的实现。