Jmix View Process Forms
Рассмотренные в предыдущем разделе Input Dialog Forms подходят для простых взаимодействий с пользователем, таких как ввод данных. Однако Jmix предоставляет возможность использовать любые экраны в качестве процессных форм. Этот подход позволяет добавить процессную форму со сложной компоновкой и поведением под ваши требования.
Чтобы экран можно было использовать в качестве процессной формы, добавьте на него аннотацию @ProcessForm.
| Старайтесь не использовать @ProcessFormна существующих экранах разработанных для других целей. Лучше создать новый экран или новый на основе существующего. | 
Создание формы на основе экрана Jmix
Выберите элемент на холсте. Затем, в панели BPMN Inspector в качестве типа формы установите Jmix view.
 
Выпадающий список позволяет выбрать существующий экран. Чтобы создать новую форму, нажмите кнопку  .
.
 
В диалоге View Creation Wizard введите названия для дескриптора и контроллера экрана, а также другие параметры:
 
Выберите шаблон формы. Доступны два варианта:
 
- 
Process form with process variables (default) – переменные процесса c типом Entity будут представлены в форме компонентом EntityPicker. Значение переменной можно будет выбрать из выпадающего списка. 
- 
Process form for entity instance – переменные процесса с типом Entity будут представлены стандартным entity detail view. Пользователи смогут увидеть все атрибуты сущности. 
При выборе Process form for entity instance может потребоваться указать класс сущности и настроить фетч-план:
 
| Некоторые настройки могут быть скрыты если используется существующая переменная. (если отмечен чекбокс Use existing variable.) | 
После завершения настройки экземпляра переменной сущности мастер предложит добавить переменные процесса. Например, добавим переменную initiator:
 
Вы можете решить, показывать ли ее в форме:
 
Далее мастер предложит указать исходы формы. По умолчанию создаются два исхода: submit и reject. Вы можете редактировать их или добавлять новые.
 
Наконец, добавьте сообщение, для формы. При локализации приложения на несколько языков, будет возможность добавить сообщения для каждого из поддерживаемых языков.
 
После завершения работы мастера откроется файл контроллера со сгенерированным кодом:
@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")
        }
)Эти параметры считываются моделером, и их можно увидеть после выбора экрана.
 
Параметры можно редактировать, можно задать значение напрямую или использовать в качестве значения одну из существующих переменных процесса.
Внутри контроллера процессной формы используйте аннотацию @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. Этот раздел доступен только для чтения.
 
Существуют ситуации, когда переменная процесса задается только при выполнении задачи с определенным выходом. Чтобы это обозначить, поместите аттрибут 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будет задана в любом из случаев. | 
Информация о выходных переменных отображается в соответствующем разделе панели свойств.
 
Ограничения использования процессных форм
По умолчанию все процессные формы экранов доступны в рамках любой модели процесса. Если вы хотите использовать какой-либо экран только в определенных процессах, нужно указать ключи процессов в атрибуте 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();
    }
}