Jmix View Process Forms

当需要一个复杂布局和行为的流程表单时, 可以用已有的 Jmix 视图 替代 输入对话框表单

@ProcessForm 注解

视图控制器需使用 @ProcessForm 注解,以便能用于流程表单。 该注解有以下属性:

  • allowedProcessKeys

  • outcomes

  • params

  • output variables

这些属性的用法在下面介绍。

不要将 @ProcessForm 注解放置于有其他用途的 Jmix 视图上。 最后通过向导或扩展已有视图的方式为 BPM 创建新的视图。

创建 Jmix 视图表单

首先在 BPMN Inspector 中为用户任务或启动事件设置 Jmix view 表单类型:

set jmix view type

然后可以从下拉列表中选择已有的 Jmix 视图流程表单。 如需创建新的视图,点击 plus button 按钮:

create jmix view

在打开的视图创建向导中,输入 XML 和控制器的名称以及其他参数:

jmix view wizard 1

然后需要确定表单模板,有两个选项: 带流程变量的流程表单(Process form with process variables)(默认)或 实体实例流程表单(Process form for entity instance)

form template select

选项解释:

  • 默认情况下,Entity 类型的流程变量在表单中显示为 EntityPicker 对象。 当用户需要从列表中选择一个实体实例时,这没问题。

  • 但是,如果用户希望能访问实体的全部属性,则 EntityPicker 组件就不合适了。 最好是能有一个类似实体标准详情视图的表单。 第二个选项支持自动生成这样的表单。

当选择创建 实体实例流程表单 时,向导中需要填写实体类和 fetch plan:

form wizard entity variable

默认情况下,Use existing variable 是勾选的,这些内容都是隐藏的。

在完成实体变量实例设置后,向导提供了继续添加流程变量的界面。 例如,可以添加一个 initiator 变量:

add variable

添加完成后,还可以选择是否在表单显示:

variable to form

向导还可以定义表单输出。 默认创建 submitreject 输出。 可以编辑或添加新的输出。

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();
    }
}

流程变量

要将流程变量传入 Jmix 视图表单中,必须将它们注入到控制器。 需要将 @ProcessVariable 注解放在注入的 UI 组件或常规类字段上。

表示当打开流程表单时,流程变量的值将写入这些字段。 如果注入的是 UI 组件,则将使用流程变量为 UI 组件赋值。

一般要求控制器中带注解的字段名称与流程变量的名称一致。 如果不一致,需要使用 @ProcessVariable 注解的可选 name 属性设置。 该属性的值需设置为流程变量名称。

@ProcessVariable
private Date date;

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

如需将流程变量更新的值返回给流程, 可以调用 ProcessFormContextsaveInjectedProcessVariables() 方法, 带注解的字段的值将保存为流程变量。

默认情况下,向导会在点击事件的处理方法中实现该行为。 或者,可以在自定义代码中实现。

ProcessFormContext

ProcessFormContext 对象包含关于将要启动的流程定义信息 (当该表单用于启动流程时)或将要完成的用户任务。

如果流程表单是在 启动流程我的任务 视图中打开,则可以使用 ProcessFormContext。 如果需要在编程时打开流程表单并带有注入的 ProcessFormContext, 那么请使用 ProcessFormViews bean。

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 指示带有 @ProcessVariables 注解的类字段值需要收集并作为流程变量存储。
4 完成任务。
5 关闭打开的窗口。

声明任务输出

在 BPM 建模器中,对于 顺序流 元素, 可以从下拉列表中选择用户任务及其输出来定义条件。 如需为使用 Jmix 视图流程表单的用户任务提供这个下拉列表的选项, 可以在表单控制器中声明一个输出列表。 需要使用 @ProcessForm 注解的 outcomes 属性。

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

流程表单参数

Jmix 视图流程表单可以接收在建模器中定义的外部参数。 表单参数在 @ProcessForm 注解的 params 属性中定义:

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

Bpmn Inspector 会读取这些参数,在选择了视图后,可以看到这些参数。

form params

可以编辑这些参数,提供直接的参数值,或使用一个已有流程变量作为参数值。

在流程表单控制器中,在类字段上使用 @ProcessFormParam 注解获取参数值。

@ProcessFormParam
private String variableName;

@ProcessFormParam
private String entityPickerCaption;

另一个获取流程表单参数完整列表的方法就是通过 ProcessFormContext 对象:

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

@ProcessVariable 注解类似,@ProcessFormParam 也支持一个可选的 name 属性。 如果未定义该属性,则使用字段名作为参数名。

参考带参数流程表单的 示例

变量输出

在对流程进行建模时, 可能需要知道 Jmix 视图流程表单中设置了哪些变量,以便稍后在流程模型中重用。 一种方法是使用 @ProcessForm 注解的 outputVariables 属性。

@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 当任务通过 approve 输出完成时,才会设置 nextActor 变量。
2 当任务通过 reject 输出完成时,才会设置 rejectionReason 变量。
3 comment 变量在任何情况都会设置。

在输出变量中会显示对于的输出类别:

output variables outcomes

限制流程表单的使用

默认情况下,所有流程表单视图在任何流程模型中都可用。 如果只想在特定流程中使用某些视图, 那么,需要在 @ProcessForm 注解的 allowedProcessKeys 属性中指定流程键值。

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

该表单仅能在 ID 为 process-1process-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 使用获取的流程定义显示启动流程表单。

启动流程表单可以与 ProcessFormContext 部分的示例类似。

使用 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);
}

任务流程表单可以与 ProcessFormContext 部分的示例类似。

示例

启动流程表单

我们看一下将流程表单用作启动表单的示例。表单展示两个字段:

  • 一个文本字段,用于输入 order number。

  • 一个实体选择器,用于选择 manager,manager 可以是流程的下一个执行人。

视图 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 声明注入的 orderNumber UI 组件是一个流程变量。由于我们开发的是流程启动表单, 这个变量还没有值,但是这个注解会在流程启动时使用。
3 与 ② 一样,但这里 manager 流程变量名与 managerEntityPicker 字段名不一致。
4 ProcessFormContext 对象启动流程。
5 当流程启动时,我们可以传入一个可选的流程实例业务键值。 这里我们用 orderNumber
6 saveInjectedProcessVariables() 方法表示, 带 @ProcessVariables 注解的字段在流程启动时会作为流程变量保存。

除了用 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

ApproveReject 按钮分别使用对应的输出完成用户任务。

视图 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();
    }
}