Jmix View Process Forms

When you need a process form with a complex layout and behavior, use Jmix views instead of input dialog forms.

@ProcessForm Annotation

A view controller should be annotated with the @ProcessForm annotation to be used as a process form. This annotation has the following attributes:

  • allowedProcessKeys

  • outcomes

  • params

  • output variables

Using of these attributes described below.

Avoid placing @ProcessForm annotation on existing Jmix views that were developed with other purposes. Better create new ones from scratch with the help of the wizard or via extending existing views.

Creating Jmix View Form

First, set a Jmix view form type for the user task or start event in the BPMN Inspector panel:

set jmix view type

Then, you’ll be able to select an existing Jmix view process form in a pull-down list. To create a new form click plus button Plus button:

create jmix view

The View Creation Wizard will open. Enter the descriptor and controller names and other parameters.

jmix view wizard 1

Now, decide about the form template. There are two options available: Process form with process variables (default) or Process form for entity instance:

form template select

About those options:

  • By default, process variables of the Entity type are represented in the form as EntityPicker objects. In the case when the user must select an entity instance from the list, it is OK.

  • But if the user wants to have access to all entity’s attributes, the EntityPicker component isn’t a suitable solution. It would be more comfortable to have a form like standard entity detail view. The second option allows to generate such a form automatically.

When selecting to create a Process form for entity instance, the wizard will ask about the entity class and fetch plan:

form wizard entity variable

However, by default, the checkbox Use existing variable is on and these details will be hidden.

After you finish with the entity variable instance setting, the wizard offers to add process variables. For example, let’s add an initiator variable:

add variable

When it is added, you can decide whether to show it in the form or not:

variable to form

Further, the wizard offers to define form outcomes. By default, submit and reject outcomes are created. You can edit them or add new ones.

outcomes

At last, define messages that will be displayed in the form. If there is more than one locale, you’ll be able to enter messages for each language.

wizard messages

Here the wizard ends its work and opens the form controller window with generated code:

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

Process Variables

To pass process variables into the Jmix view form, you have to inject them in the controller. For this purpose, place @ProcessVariable annotation on injected UI components or regular class fields.

It indicates that the value of the process variable will be written to this field when the process form is opened. In the case of the UI component, the value of the process variable will be set to the UI component.

Normally, the name of the annotated field in the controller matches the name of the process variable. If not, use optional name attribute of @ProcessVariable annotation. The value of this attribute must be the process variable name.

@ProcessVariable
private Date date;

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

To return process variables updated values back to the process, you can invoke the ProcessFormContext with the saveInjectedProcessVariables() method. In result, the values of annotated fields will be saved as process variables.

The wizard implements this behavior by default in click events handlers. Or you can do this anywhere in your custom code.

ProcessFormContext

The ProcessFormContext object contains information about a process definition to be started (when the form is used for starting the process) or a user task to be completed.

You can use ProcessFormContext if the process form is opened from the Start process and My tasks views. If you need to open the process form with the injected ProcessFormContext programmatically, use the ProcessFormViews bean.

The ProcessFormContext object also contains methods for starting the process and task completion.

An example of how to start a process:

@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 Creates a ProcessStarting instance.
2 Sets a business key to the process instance.
3 Adds a process variable.
4 Starts the actual process.
5 Closes the opened window.

An example of how to complete the user task:

@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 Creates a TaskCompletion instance.
2 Sets a task outcome.
3 Indicates that values of class fields annotated with the @ProcessVariables should be collected and saved as process variables.
4 Completes the actual task.
5 Closes the opened window.

Declare Task Outcomes

In the BPM modeler, for the sequence flow element, you can define a condition by selecting a user task and its outcome from the drop-down list. To fill this list for a user task that uses a Jmix view process form, you can declare a list of possible outcomes in the form controller. Use the outcomes attribute of the @ProcessForm annotation for that.

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

Process Form Parameters

Jmix view process forms can accept external parameters defined in the modeler. The form parameters used by the form are defined in the params attribute of the @ProcessForm annotation:

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

These parameters are read by the Bpmn Inspector, and you can see them after selecting the view.

form params

You can edit the parameters and provide a direct param value or use one of the existing process variables as a parameter value.

In the process form controller, use the @ProcessFormParam annotation on class fields to get parameter values.

@ProcessFormParam
private String variableName;

@ProcessFormParam
private String entityPickerCaption;

Another way to get a full list of process form parameters is to get them from the ProcessFormContext object:

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

As the @ProcessVariable annotation, the @ProcessFormParam supports an optional name attribute. If the attribute is not defined, then a field name is used as a parameter name.

See an example of a process form with parameters.

Output Variables

When you model the process, it may be useful to know which variables are set by the Jmix view process form in order to reuse them later in the process model. A way to achieve this is to use the outputVariabes attribute of the @ProcessForm annotation.

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

Then, you will see the Output variable section in the BPMN Inspector panel. This section is read-only.

output variables

Often there are cases when a process variable is set only when the task is completed using a particular outcome. To declare this, place the outputVariables annotation attribute to the @Outcome annotation.

@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 The nextActor variable can be set when the task is completed with the approve outcome.
2 The rejectionReason variable can be set when the task is completed with the reject outcome.
3 The comment variable can be set in any case.

Output variables with the corresponding outcomes will be displayed.

output variables outcomes

Restrict Process Form Usage

By default, all process forms views are available within any process model. If you want to use some view in particular processes only, then you should specify processes keys in the allowedProcessKeys attribute of the @ProcessForm annotation.

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

The form will be available only for processes with process-1 or process-2 process ids.

Opening Forms Programmatically

You can use the ProcessFormViews service to create start process forms and task process forms defined in the modeler.

In the example below, the start process form is opened by clicking the button in the browser view.

@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 Gets a process definition with the order-process key.
2 Shows the start process form with the received process definition.

The start process form can look like an example in the ProcessFormContext section.

To create a task form, use the openTaskProcessForm method:

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

The task process form can look like an example in the ProcessFormContext section.

Examples

Start Process Form

Let’s look at the example of the process form that is used as a start form. The form displays two fields:

  • A text field to enter the order number.

  • An entityPicker to select the manager. The manager can be the next process actor.

View XML descriptor:

<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>

View controller:

@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 The @ProcessForm annotation indicates that this view is a process form and the view will be available in the modeler.
2 We declare that the injected orderNumber UI component is a process variable. Since we develop a start process form, the variable has no value yet, but the annotation will be used on process start.
3 The same as 2, but here the manager process variable name differs from the managerEntityPicker field name.
4 ProcessFormContext is the object that we use to start the process.
5 When we start the process, we can pass an optional process instance business key. We use the orderNumber here.
6 The saveInjectedProcessVariables() indicates that values of the fields annotated with the @ProcessVariables should be saved as process variables on process start.

Instead of using saveInjectedProcessVariables() method you can explicitly set process variables:

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

Task Process Form

Let’s look at the example of the task process form that displays two fields:

  • The first one will display a value of the existing process variable - orderNumber.

  • The second field will be used for the new process variable - comment.

Approve and Reject buttons complete the user task with the corresponding outcome.

View XML descriptor:

<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>

View controller:

@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 The form defines two possible outcomes that can be used in a sequence flow node condition in the modeler. This information is used by the modeler only.
2 The orderNumber variable has been already set on process start. Because of the @ProcessVariable annotation, the value of the orderNumber process variables will be set to the orderNumber text field when the form is displayed.
3 The comment variable is not set yet, but the @ProcessVariable annotation will be taken into account when we complete the task in the button click listener.
4 Values of all fields annotated with the @ProcessVariable will be saved as process variables on task completion.
5 An alternative way to define process variables. Instead of using the saveInjectedProcessVariables() method, you can define process variables directly.

Process Form with Parameters

Let’s assume that you need a form for the next process actor selection. The form should display EntityPicker field with users and save the result into a process variable. We want to use the form for selecting different actors at different process steps, so the form should have two parameters:

  • variableName

  • entityPickerCaption

View XML descriptor:

<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>

View controller:

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