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 |
Creating Jmix View Form
First, set a Jmix view form type for the user task or start event in the BPMN Inspector panel:
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:
The View Creation Wizard will open. Enter the descriptor and controller names and other parameters.
Now, decide about the form template. There are two options available: Process form with process variables (default) or Process form for entity instance:
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:
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:
When it is added, you can decide whether to show it in the form or not:
Further, the wizard offers to define form outcomes.
By default, submit
and reject
outcomes are created.
You can edit them or add new ones.
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.
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.
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.
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.
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();
}
}