Jmix View Process Forms

Рассмотренные в предыдущем разделе Input Dialog Forms подходят для простых взаимодействий с пользователем, таких как ввод данных. Однако Jmix предоставляет возможность использовать любые экраны в качестве процессных форм. Этот подход позволяет добавить процессную форму со сложной компоновкой и поведением под ваши требования.

Чтобы экран можно было использовать в качестве процессной формы, добавьте на него аннотацию @ProcessForm.

Старайтесь не использовать @ProcessForm на существующих экранах разработанных для других целей. Лучше создать новый экран или новый на основе существующего.

Создание формы на основе экрана Jmix

Выберите элемент на холсте. Затем, в панели BPMN Inspector в качестве типа формы установите Jmix view.

set jmix view type

Выпадающий список позволяет выбрать существующий экран. Чтобы создать новую форму, нажмите кнопку plus button.

create jmix view

В диалоге View Creation Wizard введите названия для дескриптора и контроллера экрана, а также другие параметры:

jmix view wizard 1

Выберите шаблон формы. Доступны два варианта:

form template select
  • Process form with process variables (default) – переменные процесса c типом Entity будут представлены в форме компонентом EntityPicker. Значение переменной можно будет выбрать из выпадающего списка.

  • Process form for entity instance – переменные процесса с типом Entity будут представлены стандартным entity detail view. Пользователи смогут увидеть все атрибуты сущности.

При выборе Process form for entity instance может потребоваться указать класс сущности и настроить фетч-план:

form wizard entity variable
Некоторые настройки могут быть скрыты если используется существующая переменная. (если отмечен чекбокс Use existing variable.)

После завершения настройки экземпляра переменной сущности мастер предложит добавить переменные процесса. Например, добавим переменную initiator:

add variable

Вы можете решить, показывать ли ее в форме:

variable to form

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

outcomes

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

wizard messages

После завершения работы мастера откроется файл контроллера со сгенерированным кодом:

@ProcessForm(outcomes = {
        @Outcome(id = "submit"),
        @Outcome(id = "reject")
}, outputVariables = {
        @OutputVariable(name = "initiator", type = User.class),
        @OutputVariable(name = "orderVar", type = Order.class)
})
@Route(value = "order-approval-form", layout = MainView.class)
@ViewController("smpl_OrderApprovalForm")
@ViewDescriptor("order-approval-form.xml")
public class OrderApprovalForm extends StandardView {

    @Autowired
    private ProcessFormContext processFormContext;
    @ProcessVariable(name = "initiator")
    @ViewComponent
    private EntityPicker<User> initiatorField;
    @ProcessVariable(name = "orderVar")
    private Order orderVar;
    @ViewComponent
    DataContext dataContext;
    @ViewComponent
    private InstanceContainer<Order> orderDc;

    @Subscribe
    public void onBeforeShow(final BeforeShowEvent event) {
        if (orderVar == null) {
            orderVar = dataContext.create(Order.class);
        }
        orderDc.setItem(dataContext.merge(orderVar));
    }

    @Subscribe(id = "submitBtn", subject = "clickListener")
    protected void onSubmitBtnClick(ClickEvent<JmixButton> event) {
        dataContext.save();
        processFormContext.taskCompletion()
                .withOutcome("submit")
                .saveInjectedProcessVariables()
                .complete();
        closeWithDefaultAction();
    }

    @Subscribe(id = "rejectBtn", subject = "clickListener")
    protected void onRejectBtnClick(ClickEvent<JmixButton> event) {
        dataContext.save();
        processFormContext.taskCompletion()
                .withOutcome("reject")
                .saveInjectedProcessVariables()
                .complete();
        closeWithDefaultAction();
    }
}

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

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

@ProcessVariable
private Date date;

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

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

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

ProcessFormContext

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

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

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

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

@ProcessVariable
private Date date;

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

@Autowired
private ProcessFormContext processFormContext;

@Subscribe("startProcessBtn")
public void onStartProcessBtnClick(ClickEvent<JmixButton> 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 Закрытие текущего окна.

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

@Autowired
private ProcessFormContext processFormContext;

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

Указание Исходов Задачи

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

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

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

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

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

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

form params

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

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

@ProcessFormParam
private String variableName;

@ProcessFormParam
private String entityPickerCaption;

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

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

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

Ниже привёден пример процессной формы с параметрами.

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

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

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

После этого в панели BPMN Inspector появится раздел Output variable. Этот раздел доступен только для чтения.

output variables

Существуют ситуации, когда переменная процесса задается только при выполнении задачи с определенным выходом. Чтобы это обозначить, поместите аттрибут 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 outcomes

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

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

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

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

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

Сервис ProcessFormViews позволяет программно создавать формы запуска процесса и пользовательских задач определенных в моделере.

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

@Autowired
private RepositoryService repositoryService;

@Autowired
protected ProcessFormViews processFormViews;

@Subscribe("startProcessBtn")
public void onStartProcessBtnClick(final ClickEvent<JmixButton> event) {
    ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery() (1)
            .processDefinitionKey("order-process")
            .latestVersion()
            .singleResult();

    processFormViews.openStartProcessForm(processDefinition, this); (2)
}
1 Получение определения процесса по ключу order-process.
2 Отображение процессной формы.

Чтобы создать форму для пользовательской задачи, используйте метод openTaskProcessForm:

@Autowired
private TaskService taskService;

@Autowired
private ProcessFormViews processFormViews;

@Subscribe("openTaskBtn")
public void onOpenTaskBtnClick(ClickEvent<JmixButton> event) {

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

    processFormViews.openTaskProcessForm(task, this);
}

Примеры

Процессная Форма Запуска

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

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

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

XML дескриптор:

<view xmlns="http://jmix.io/schema/flowui/view"
      title="msg://com.company.bpmex1.view.forms/startProcessForm.title">
    <layout>
        <formLayout>
            <textField id="orderNumber"
                       label="msg://com.company.bpmex1.view.forms/orderNumber"
                       datatype="string"/>
            <entityPicker id="managerEntityPicker"
                          metaClass="User"
                          label="msg://managerEntityPicker.caption">
                <actions>
                    <action id="lookup" type="entity_lookup"/>
                    <action id="clear" type="entity_clear"/>
                </actions>
            </entityPicker>
        </formLayout>
        <hbox id="actionsPanel" spacing="true">
            <button id="startProcessBtn"
                    icon="CHECK"
                    text="msg://com.company.bpmex1.view.forms/startProcessBtn.text"/>
        </hbox>
    </layout>
</view>

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

@ViewController("OrderApprovalStartForm")
@ViewDescriptor("order-approval-start-form.xml")
@ProcessForm (1)
public class OrderApprovalStartForm extends StandardView {

    @ViewComponent
    @ProcessVariable (2)
    private TypedTextField<String> orderNumber;

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

    @Autowired
    private ProcessFormContext processFormContext; (4)

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

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

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

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

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

  • Первое поле соответствует существующей переменной процесса – orderNumber.

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

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

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

<view xmlns="http://jmix.io/schema/flowui/view"
      title="msg://orderApprovalTaskForm.title">
    <layout>
        <formLayout>
            <textField id="orderNumber" readOnly="true"
                       label="msg://orderNumber"/>
            <textField id="commentField" label="msg://comment"/>
        </formLayout>
        <hbox id="actionsPanel" spacing="true">
            <button id="approveBtn" icon="CHECK" text="msg://approveBtn.text"/>
            <button id="rejectBtn" icon="BAN" text="msg://rejectBtn.text"/>
        </hbox>
    </layout>
</view>

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

@ViewController("OrderApprovalTaskForm")
@ViewDescriptor("order-approval-task-form.xml")
@ProcessForm(
        outcomes = { (1)
                @Outcome(id = "approve"),
                @Outcome(id = "reject")
        }
)
public class OrderApprovalTaskForm extends StandardView {

    @ViewComponent
    @ProcessVariable (2)
    private TypedTextField<String> orderNumber;

    @ViewComponent
    @ProcessVariable(name = "comment") (3)
    private TypedTextField<String> commentField;

    @Autowired
    private ProcessFormContext processFormContext;

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

    @Subscribe("rejectBtn")
    protected void onRejectBtnClick(ClickEvent<JmixButton> 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(), вы можете определить переменные процесса напрямую.

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

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

  • variableName

  • entityPickerCaption

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

<view xmlns="http://jmix.io/schema/flowui/view"
      title="msg://com.company.bpmex1.view.forms/actorSelectionForm.title">
    <layout spacing="true">
        <formLayout width="20em">
            <entityPicker id="userEntityPicker"
                          metaClass="User"
                          property="username">
                <actions>
                    <action id="lookup" type="entity_lookup"/>
                    <action id="clear" type="entity_clear"/>
                </actions>
            </entityPicker>
        </formLayout>
        <hbox spacing="true">
            <button id="completeTaskBtn" icon="CHECK" text="msg://completeTask"/>
        </hbox>
    </layout>
</view>

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

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

    @Autowired
    private ProcessFormContext processFormContext;

    @ViewComponent
    private EntityPicker<String> userEntityPicker;

    @ProcessFormParam
    private String variableName;

    @ProcessFormParam
    private String entityPickerCaption;

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

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