Экран Jmix

Если вам нужна процессная форма со сложной компоновкой и поведением, вы можете использовать существующий экран UI Jmix вместо диалоговых форм ввода. Контроллер экрана должен быть помечен аннотацией @ProcessForm, чтобы использоваться в качестве процессной формы.

Аннотация @ProcessForm указывает, что экран должен появиться в поле со списком процессных форм моделера.

Переменные процесса

Аннотацией @ProcessVariable можно помечать инжектированные компоненты UI или обычные поля класса.

Она указывает, что значение переменной процесса будет записано в это поле при открытии процессной формы. В случае компонента UI значением переменной процесса будет компонент UI.

@ProcessVariable
private Date date;

@Autowired
@ProcessVariable(name = "order")
private EntityPicker<Order> orderEntityPicker;

Если настроить ProcessFormContext с помощью метода saveInjectedProcessVariables(), то значения аннотированных полей будут сохранены как переменные процесса при запуске процесса или завершении пользовательской задачи.

Аннотация @ProcessVariable имеет необязательный атрибут name. Значением этого атрибута является имя переменной процесса. Если атрибут name отсутствует, то в качестве имени переменной процесса используется имя поля.

ProcessFormContext

Объект ProcessFormContext содержит информацию об определении запускаемого процесса (когда для запуска процесса используется форма) или пользовательской задачи, которую необходимо выполнить.

ProcessFormContext можно использовать, если процессная форма открыта из экранов Start process и My tasks. Если вам нужно открыть процессную форму с инжектированным ProcessFormContext программно, используйте бин ProcessFormScreens.

Объект ProcessFormContext также содержит методы для запуска процесса и завершения задачи.

Пример запуска процесса:

StartProcessForm.java
@ProcessVariable
private Date date;

@Autowired
@ProcessVariable(name = "order")
private EntityPicker<Order> orderEntityPicker;

@Autowired
private ProcessFormContext processFormContext;

@Subscribe("startProcessBtn")
public void onStartProcessBtnClick(Button.ClickEvent event) {
    processFormContext.processStarting() (1)
            .withBusinessKey("order-1") (2)
            .addProcessVariable("date", date)
            .addProcessVariable("order", orderEntityPicker.getValue()) (3)
            .start(); (4)
    closeWithDefaultAction(); (5)
}
1 Создание экземпляра ProcessStarting.
2 Установка бизнес-ключа для экземпляра процесса.
3 Добавление переменной процесса.
4 Запуск процесса.
5 Закрытие открытого окна.

Пример завершения пользовательской задачи:

TaskApprovalForm.java
@Autowired
private ProcessFormContext processFormContext;

@Subscribe("rejectBtn")
protected void onRejectBtnClick(Button.ClickEvent event) {
    processFormContext.taskCompletion() (1)
            .withOutcome("reject") (2)
            .saveInjectedProcessVariables() (3)
            .complete(); (4)
    closeWithDefaultAction(); (5)
}
1 Создание экземпляра TaskCompletion.
2 Установка выхода задачи.
3 Указывает, что значения полей класса, аннотированные символом, @ProcessVariables, должны быть собраны и сохранены как переменные процесса.
4 Завершение задачи.
5 Закрытие открытого окна.

Объявление выходов задачи

В моделере можно определить условие для элемента потока, выбрав пользовательскую задачу и ее выход из выпадающего списка. Чтобы заполнить этот список для пользовательской задачи, которая использует процессную форму экрана Jmix, вы можете объявить список возможных выходов в контроллере формы. Используйте для этого атрибут outcomes аннотации @ProcessForm.

@ProcessForm(
        outcomes = { (1)
                @Outcome(id = "approve"),
                @Outcome(id = "reject")
        }
)
public class TaskApprovalForm extends Screen {

Параметры процессных форм

Процессные формы экранов Jmix могут принимать внешние параметры, определенные в моделере. Параметры, используемые формой, определяются в атрибуте params аннотации @ProcessForm:

@ProcessForm(
        params = {
                @Param(name = "variableName"),
                @Param(name = "entityPickerCaption")
        }
)

Эти параметры считываются моделером, и их можно увидеть после выбора экрана.

form params

Вы можете редактировать параметры и указать прямое значение параметра или использовать в качестве значения одну из существующих переменных процесса.

form params edit

Внутри контроллера процессной формы используйте аннотацию @ProcessFormParam на полях класса, чтобы получить значения параметров.

@ProcessFormParam
private String variableName;

@ProcessFormParam
private String entityPickerCaption;

Полный список параметров процессной формы также можно получить из объекта ProcessFormContext:

List<FormParam> formParams = processFormContext.getFormData().getFormParams();

Как и аннотация @ProcessVariable, @ProcessFormParam поддерживает необязательный атрибут name. Если атрибут не определен, то в качестве имени параметра используется имя поля.

См. пример процессной формы с параметрами.

Выходные переменные

При моделировании процесса может быть полезно знать, какие переменные задаются процессной формой экрана Jmix, чтобы позже использовать их повторно в модели процесса. Для этого используйте атрибут outputVariabes аннотации @ProcessForm.

@ProcessForm(
        outputVariables = {
                @OutputVariable(name = "order", type = Order.class),
                @OutputVariable(name = "comment", type = String.class)
        }
)

Часто бывают случаи, когда переменная процесса устанавливается только тогда, когда задача выполнена с использованием определенного выхода. Для объявления этого поместите атрибут аннотации outputVariables в аннотацию @Outcome.

@ProcessForm(
        outcomes = {
                @Outcome(
                        id = "approve",
                        outputVariables = {
                                @OutputVariable(name = "nextActor", type = User.class) (1)
                        }
                ),
                @Outcome(
                        id = "reject",
                        outputVariables = {
                                @OutputVariable(name = "rejectionReason", type = String.class) (2)
                        }
                )
        },
        outputVariables = {
                @OutputVariable(name = "comment", type = String.class) (3)
        }
)
1 Переменная nextActor может быть установлена, когда задача завершена с выходом approve.
2 Переменная rejectionReason может быть установлена, когда задача завершена с выходом reject.
3 Переменная comment может быть установлена в любом случае.

Информация о выходных переменных отображается в соответствующем разделе панели свойств при выборе процессной формы.

output variables

Ограничение использования процессных форм

По умолчанию все процессные формы экранов доступны в рамках любой модели процесса. Если вы хотите использовать какой-либо экран только в определенных процессах, нужно указать ключи процессов в атрибуте allowedProcessKeys аннотации @ProcessForm.

@ProcessForm(allowedProcessKeys = {"process-1", "process-2"})

Форма будет доступна только для процессов с идентификаторами process-1 и process-2 в моделере.

Программное открытие форм

Вы можете использовать службу ProcessFormScreens для создания процессных форм запуска и процессных форм задач, определенных в моделере.

В приведенном ниже примере процессная форма запуска открывается нажатием кнопки на экране браузера.

@Autowired
private RepositoryService repositoryService;

@Autowired
protected ProcessFormScreens processFormScreens;

@Subscribe("startProcBtn")
public void onStartProcBtnClick(Button.ClickEvent event) {
    ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery() (1)
            .processDefinitionKey("order-process")
            .latestVersion()
            .singleResult();

    Screen startProcessForm = processFormScreens.createStartProcessForm(processDefinition, this); (2)
    startProcessForm.show(); (3)
}
1 Получение определения процесса с ключом order-process.
2 Создание процессной формы запуска с полученным определением процесса.
3 Отображение процессной формы запуска order-process.

Процессная форма запуска может выглядеть как в примере в разделе ProcessFormContext.

Для создания формы задачи используйте метод createTaskProcessForm:

@Autowired
private TaskService taskService;

@Autowired
private ProcessFormScreens processFormScreens;

@Subscribe("openTaskBtn")
public void onOpenTaskBtnClick(Button.ClickEvent event) {

    Task task = taskService.createTaskQuery()
            .processDefinitionKey("approve-order-process")
            .taskAssignee("admin")
            .active()
            .orderByTaskCreateTime()
            .list()
            .get(0);

    Screen taskProcessForm = processFormScreens.createTaskProcessForm(task, this);
    taskProcessForm.show();
}

Процессная форма задачи может выглядеть как пример в разделе ProcessFormContext.

Примеры

Процессная форма запуска

Давайте рассмотрим пример процессной формы, которая используется в качестве стартовой. В форме отображаются два поля:

  • текстовое поле для ввода номера заказа.

  • выпадающий список пользователей для выбора менеджера. Менеджер может быть следующим актором процесса.

XML-дескриптор экрана:

<window xmlns="http://jmix.io/schema/ui/window"
        caption="msg://startProcessForm.caption">
    <layout expand="actionsPanel" spacing="true">
        <form>
            <textField id="orderNumber" caption="msg://orderNumber.caption"/>
            <entityPicker id="managerEntityPicker"
                          metaClass="smpl_User"
                          caption="msg://managerEntityPicker.caption">
                <actions>
                    <action id="lookup" type="entity_lookup"/>
                    <action id="clear" type="entity_clear"/>
                </actions>
            </entityPicker>
        </form>
        <hbox id="actionsPanel" spacing="true">
            <button id="startProcessBtn" icon="font-icon:CHECK" caption="msg://startProcessBtn.caption"/>
        </hbox>
    </layout>
</window>

Контроллер экрана:

@UiController("smpl_StartProcessForm")
@UiDescriptor("start-process-form.xml")
@ProcessForm (1)
public class StartProcessForm extends Screen {

    @Autowired
    @ProcessVariable (2)
    private TextField<String> orderNumber;

    @Autowired
    @ProcessVariable(name = "manager") (3)
    private EntityPicker<User> managerEntityPicker;

    @Autowired
    private ProcessFormContext processFormContext; (4)

    @Subscribe("startProcessBtn")
    public void onStartProcessBtnClick(Button.ClickEvent event) {
        processFormContext.processStarting()
                .withBusinessKey(orderNumber.getValue()) (5)
                .saveInjectedProcessVariables() (6)
                .start();
        closeWithDefaultAction();
    }
}
1 Аннотация @ProcessForm указывает, что этот экран является процессной формой и будет доступен в моделере.
2 Мы объявляем, что инжектированный компонент UI orderNumber является переменной процесса. Поскольку мы разрабатываем процессную форму запуска, переменная еще не имеет значения, но аннотация будет использоваться при запуске процесса.
3 То же, что и 2, но здесь имя manager переменной процесса отличается от имени поля managerEntityPicker.
4 ProcessFormContext – это объект, который мы используем для запуска процесса.
5 При запуске процесса мы можем передать необязательный бизнес-ключ экземпляра процесса. Здесь мы использовали orderNumber.
6 saveInjectedProcessVariables() указывает, что значения полей с аннотацией @ProcessVariables должны быть сохранены как переменные процесса при запуске процесса.

Вместо того, что бы использовать метод saveInjectedProcessVariables(), вы можете задать переменные процесса явно:

@Subscribe("startProcessBtn")
public void onStartProcessBtnClick(Button.ClickEvent event) {
    processFormContext.processStarting()
            .withBusinessKey(orderNumber.getValue())
            .addProcessVariable("orderNumber", orderNumber.getValue())
            .addProcessVariable("manager",managerEntityPicker.getValue())
            .start();
    closeWithDefaultAction();
}

Процессная форма задачи

Рассмотрим пример процессной формы задачи с двумя полями:

  • Первый отобразит значение существующей переменной процесса – orderNumber.

  • Второе поле будет использоваться для новой переменной процесса – comment.

Кнопки Approve и Reject завершают пользовательскую задачу с соответствующим выходом.

XML-дескриптор экрана:

<window xmlns="http://jmix.io/schema/ui/window"
        caption="msg://taskApprovalForm.caption">
    <layout expand="actionsPanel" spacing="true">
        <form>
            <textField id="orderNumber" editable="false" caption="msg://orderNumber.caption"/>
            <textField id="commentField" caption="msg://comment"/>
        </form>
        <hbox id="actionsPanel" spacing="true">
            <button id="approveBtn" icon="font-icon:CHECK" caption="msg://approveBtn.caption"/>
            <button id="rejectBtn" icon="font-icon:BAN" caption="msg://rejectBtn.caption"/>
        </hbox>
    </layout>
</window>

Контроллер экрана:

@UiController("smpl_TaskApprovalForm")
@UiDescriptor("task-approval-form.xml")
@ProcessForm(
        outcomes = { (1)
                @Outcome(id = "approve"),
                @Outcome(id = "reject")
        }
)
public class TaskApprovalForm extends Screen {

    @Autowired
    @ProcessVariable (2)
    private TextField<String> orderNumber;

    @Autowired
    @ProcessVariable(name = "comment") (3)
    private TextField<String> commentField;

    @Autowired
    private ProcessFormContext processFormContext;

    @Subscribe("approveBtn")
    protected void onApproveBtnClick(Button.ClickEvent event) {
        processFormContext.taskCompletion()
                .withOutcome("approve")
                .saveInjectedProcessVariables() (4)
                .complete();
        closeWithDefaultAction();
    }

    @Subscribe("rejectBtn")
    protected void onRejectBtnClick(Button.ClickEvent event) {
        processFormContext.taskCompletion()
                .withOutcome("reject")
                .addProcessVariable("comment", commentField.getValue()) (5)
                .complete();
        closeWithDefaultAction();
    }
}
1 Форма определяет два возможных выхода, которые можно использовать в условии узла потока в моделере. Эта информация используется только моделером.
2 Переменная orderNumber уже была установлена при запуске процесса. Из-за аннотации @ProcessVariable значение переменных процесса orderNumber будет установлено в текстовом поле orderNumber при отображении формы.
3 Переменная comment еще не задана, но аннотация @ProcessVariable будет учтена слушателем нажатия кнопки при выполнении задачи.
4 Значения всех полей с аннотацией @ProcessVariable будут сохранены как переменные процесса по завершении задачи.
5 Альтернативный способ определения переменных процесса. Вместо того, что бы использовать метод saveInjectedProcessVariables(), вы можете определить переменные процесса напрямую.

Процессная форма StandardEditor

Этот пример демонстрирует, как использовать StandardEditor в качестве процессной формы. Это может быть полезно, если вы храните какую-либо сущность в переменной процесса и хотите просматривать или редактировать поля сущности с помощью процессной формы задачи.

Предположим, что мы добавили кнопку Start process в стандартный редактор сущностей Order. Кнопка Start process запускает процесс программно и помещает редактируемый экземпляр сущности Order в переменные процесса.

@Autowired
private RuntimeService runtimeService;

@Subscribe("startProcessBtn")
public void onStartProcessBtnClick(Button.ClickEvent event) {
    Order order = getEditedEntity();
    Map<String, Object> processVariables = new HashMap<>();
    processVariables.put("order", order); (1)
    runtimeService.startProcessInstanceByKey("order-approval", (2)
            order.getNumber(),
            processVariables);
    closeWithCommit();
}
1 Размещение редактируемого объекта в переменную процесса order.
2 Запускает процесс с id order-approval, номером заказа в качестве бизнес-ключа и мапом с переменными процесса.

Например, XML-дескриптор процессной формы для следующей пользовательской задачи может выглядеть так:

<window xmlns="http://jmix.io/schema/ui/window"
        caption="msg://orderEdit2.caption"
        focusComponent="form">
    <data>
        <instance id="orderDc"
                  class="bpm.ex1.entity.Order" fetchPlan="_local">
            <loader/>
        </instance>
    </data>
    <facets>
        <dataLoadCoordinator auto="true"/>
        <screenSettings id="settingsFacet" auto="true"/>
    </facets>
    <actions>
        <action id="windowCommitAndClose" caption="msg:///actions.Ok"
                icon="EDITOR_OK"
                primary="true"
                shortcut="${COMMIT_SHORTCUT}"/>
        <action id="windowClose"
                caption="msg:///actions.Close"
                icon="EDITOR_CANCEL"/>
    </actions>
    <dialogMode height="600"
                width="800"/>
    <layout spacing="true" expand="editActions">
        <form id="form" dataContainer="orderDc">
            <column width="350px">
                <textField id="numberField" property="number"/>
                <dateField id="dateField" property="date"/>
                <entityPicker id="customerField" property="customer">
                    <actions>
                        <action id="lookup" type="entity_lookup"/>
                        <action id="clear" type="entity_clear"/>
                    </actions>
                </entityPicker>
                <comboBox id="productField" property="product"/>
                <textField id="amountField" property="amount"/>
            </column>
        </form>
        <hbox id="editActions" spacing="true">
            <button id="completeTaskBtn" caption="msg://completeTaskBtn.caption"/>
        </hbox>
    </layout>
</window>

Единственное, что отличает XML-дескриптор формы от обычного редактора сущностей, это то, что мы заменили панель editActions на кнопку Complete task.

Контроллер экрана процессной формы:

@UiController("smpl_OrderEditTaskForm")
@UiDescriptor("order-edit-task-form.xml")
@EditedEntityContainer("orderDc")
@ProcessForm (1)
public class OrderEditTaskForm extends StandardEditor<Order> {

    @ProcessVariable
    protected Order order; (2)

    @Autowired
    protected ProcessFormContext processFormContext;

    @Subscribe
    protected void onInit(InitEvent event) {
        setEntityToEdit(order); (3)
    }

    @Subscribe("completeTaskBtn")
    protected void onCompleteTaskBtnClick(Button.ClickEvent event) {
        commitChanges() (4)
                .then(() -> {
                    processFormContext.taskCompletion()
                            .complete();
                    closeWithDefaultAction();
                });
    }
1 Аннотация @ProcessForm указывает, что экран можно использовать как процессную форму.
2 Инжектирование переменной процесса order.
3 К моменту срабатывания слушателя InitEvent уже должны быть установлены значения полей @ProcessVariable. Мы вызываем метод setEntityToEdit() класса StandardEditor – этот метод перезагружает сущность order с представлением, необходимым для экрана редактора, и задает сущность контейнеру данных.
4 При нажатии кнопку Complete task происходит коммит редактора и выполняется завершение задачи.

Процессная форма с параметрами

Предположим, вам нужна форма для выбора следующего актора процесса. Форма должна отображать поле EntityPicker с пользователями и сохранять результат в переменной процесса. Мы хотим использовать форму для выбора разных акторов на разных этапах процесса, поэтому форма должна иметь два параметра:

  • variableName

  • entityPickerCaption

XML-дескриптор экрана:

<window xmlns="http://jmix.io/schema/ui/window"
        caption="msg://actorSelectionForm.caption">
    <layout spacing="true">
        <form width="400px">
            <entityPicker id="userEntityPicker"
                          metaClass="smpl_User"
                          property="username">
            <actions>
                <action id="lookup" type="entity_lookup"/>
                <action id="clear" type="entity_clear"/>
            </actions>
            </entityPicker>
        </form>
        <hbox spacing="true">
            <button id="completeTaskBtn" icon="font-icon:CHECK" caption="msg://completeTask"/>
        </hbox>
    </layout>
</window>

Контроллер экрана:

[source,java,indent=0]s

@ProcessForm(
        params = {
                @Param(name = "variableName"),
                @Param(name = "entityPickerCaption")
        }
)
public class ActorSelectionForm extends Screen {

    @Autowired
    private ProcessFormContext processFormContext;

    @Autowired
    private EntityPicker<String> userEntityPicker;

    @ProcessFormParam
    private String variableName;

    @ProcessFormParam
    private String entityPickerCaption;

    @Subscribe
    private void onBeforeShow(BeforeShowEvent event) {
        userEntityPicker.setCaption(entityPickerCaption);
    }

    @Subscribe("completeTaskBtn")
    private void onCompleteTaskBtnClick(Button.ClickEvent event) {
        processFormContext.taskCompletion()
                .addProcessVariable(variableName, userEntityPicker.getValue())
                .complete();
        closeWithDefaultAction();
    }
}