This reference covers the features of the Camunda BPM Forms SDK. The Forms SDK simplifies the implementation of user task forms in HTML5 / JavaScript based Applications. The Forms SDK itself is written in JavaScript and can be added to any JavaScript based Application.
The Forms SDK provides the following features:
The following is a simple example of a form with two input fields binding to process variables
CUSTOMER_ID
and CUSTOMER_REVENUE
:
<form>
<label for="customerId">Customer Id:</label>
<input type="text" id="customerId"
cam-variable-name="CUSTOMER_ID"
cam-variable-type="String">
<label for="customerRevenue">Customer Revenue:</label>
<input type="text" id="customerRevenue"
cam-variable-name="CUSTOMER_REVENUE"
cam-variable-type="Float">
</form>
The Forms SDK is intended to be lean and small. By design it is not concerned with things like
The Forms SDK provides a set of directives which simplify working with process variables in an HTML form. These directives work on most of the HTML controls and allow users to declaratively fetch variables from the process engine and have their values written to and read from input fields.
If an HTML control is not supported, you need to write custom JavaScript.
cam-variable-name
DirectiveThe cam-variable-name
directive allows providing the name of a process / task / case variable. If the directive is discovered on an HTML control, the value of the variable is fetched from the server and written to the control.
<input type="text"
cam-variable-name="CUSTOMER_ID">
cam-variable-name
If you use the AngularJS integration, the cam-variable-name
directive will automatically bind the input to the model in case no binding is provided by the user.
The following two markup examples have the same semantics:
<input type="text"
cam-variable-name="CUSTOMER_ID">
is the same as
<input type="text"
cam-variable-name="CUSTOMER_ID"
ng-model="CUSTOMER_ID">
If the user provides a customer ng-model
binding, it is respected:
<input type="text"
cam-variable-name="CUSTOMER_ID"
ng-model="customerId">
Current value: {{customerId}}
cam-variable-type
DirectiveThe cam-variable-type
directive allows specifying the type of a process / task / vase variable. This is required if the variable does not yet exist.
The following markup creates a text input field bound to a variable of type Double
:
<input type="text"
cam-variable-name="INVOICE_AMOUNT"
cam-variable-type="Double">
See the section on variable types for a list of variable types which are supported out of the box.
cam-variable-type
The cam-variable-type
directive can be used as validation directive:
<input type="text"
name="invoiceAmount"
cam-variable-name="INVOICE_AMOUNT"
cam-variable-type="Double">
<span ng-show="myForm.invoiceAmount.$error.camVariableType">
Input must be a 'Double'.
</span>
Single line text inputs are <input type="text">
controls. Single line text inputs are the most
common input field and allow the user to provide values for different data types.
A text input can be bound to a process variable using the cam-variable-type
and
cam-variable-name
directives:
<input type="text"
cam-variable-name="CUSTOMER_ID"
cam-variable-type="String" />
In the example above, the text input field is bound to the variable named CUSTOMER_ID
of type
String
.
A text input field supports multiple variable types.
Binding to existing variables: Note that if you bind the input field to an existing variable, the type of the variable is provided by the process engine and the
cam-variable-type
directive is not required.
In order to bind the input field to a String
variable, the directive cam-variable-type="String"
must be used.
Example:
<input type="text"
cam-variable-name="CUSTOMER_ID"
cam-variable-type="String" />
Trimming: Note that the value of the String variable is trimmed before it is submitted to the process engine: leading and trailing whitespace is removed.
In order to bind the input field to a Java Integer
variable, the directive
cam-variable-type="Integer"
must be used.
Example:
<input type="text"
cam-variable-name="CUSTOMER_AGE"
cam-variable-type="Integer" />
In order to bind the input field to a Java Long
variable, the directive
cam-variable-type="Long"
must be used.
Example:
<input type="text"
cam-variable-name="CUSTOMER_REVENUE"
cam-variable-type="Long" />
In order to bind the input field to a Java Short
variable, the directive
cam-variable-type="Short"
must be used.
Example:
<input type="text"
cam-variable-name="CUSTOMER_REVENUE"
cam-variable-type="Short" />
In order to bind the input field to a Java Float
variable, the directive
cam-variable-type="Float"
must be used.
Example:
<input type="text"
cam-variable-name="CUSTOMER_REVENUE"
cam-variable-type="Float" />
In order to bind the input field to a Java Double
variable, the directive
cam-variable-type="Double"
must be used.
Example:
<input type="text"
cam-variable-name="CUSTOMER_REVENUE"
cam-variable-type="Double" />
Textareas are HTML <textarea>
elements of the form
<textarea></textarea>
A textarea input can be bound to a process variable using the cam-variable-type
and
cam-variable-name
directives:
<textarea cam-variable-name="CUSTOMER_ADDRESS"
cam-variable-type="String">
</textarea>
In the example above, the textarea is bound to the variable named CUSTOMER_ADDRESS
of type
String
.
The textarea supports the same variable types as the single line text input <input
type="text"></input>
.
Date input is supported using a <input type="text">
control.
In order to bind the input field to a Java Date
variable, the directive
cam-variable-type="Date"
must be used.
Example:
<input type="text"
cam-variable-name="CONTRACT_START_DATE"
cam-variable-type="Date" />
Currently only the ISO Date Format yyyy-MM-dd'T'HH:mm:ss
is supported.
Example value: 2013-01-23T13:42:42
The Form SDK itself does not provide any custom components of widgets. As such it also does not provide a date picker. However, you are able to integrate third party libraries providing such widgets.
Inside Camunda Tasklist, datepicker support is provided through Angular UI.
You can use the Angular UI datepicker directive to offer a datepicker for the date input field. The complete markup of the input field including the datepicker button is shown below.
<p class="input-group">
<input type="text"
cam-variable-name="CONTRACT_START_DATE"
cam-variable-type="Date"
class="form-control"
datepicker-popup="yyyy-MM-dd'T'HH:mm:ss"
is-open="dateFieldOpened" />
<span class="input-group-btn">
<button type="button"
class="btn btn-default"
ng-click="open($event)">
<i class="glyphicon glyphicon-calendar"></i>
</button>
</span>
</p>
In addition to the HTML markup, the following JavaScript must be included in the form file (see Custom JavaScript):
<script cam-script type="text/form-script">
$scope.open = function($event) {
$event.preventDefault();
$event.stopPropagation();
$scope.dateFieldOpened = true;
};
</script>
The attributes of the datepicker component are explained below:
Additional attributes of the input element:
datepicker-popup="yyyy-MM-dd'T'HH:mm:ss"
: This attribute sets the format of the date which
is returned by the datepicker. It must be the ISO Date Format.is-open="dateFieldOpened"
: This attribute contains the name of the variable, which
indicates the open status of the datepicker. It must be the same variable, which is set to
true in the open
function in the JavaScript snippet. If a form contains multiple
datepickers, they must have different values for this attribute.Attributes of the datepicker button:
ng-click="open($event)"
: This attribute contains the name of the function which is called
when the datepicker button is clicked. It must be the function name of the JavaScript snippet
which sets the is-open
variable to true. If a form contains multiple date pickers, they
must have different function names, or the name of the is-open
variable must be passed to
the function.Checkboxes are HTML <input type="checkbox">
controls. Checkbox controls can be used for boolean
variable types.
A checkbox input can be bound to a process variable using the cam-variable-type
and cam-variable-name
directives:
<input type="checkbox"
cam-variable-name="IS_VIP_CUSTOMER"
cam-variable-type="Boolean" />
In the example above, the checkbox is bound to the variable named IS_VIP_CUSTOMER
of type
Boolean
.
The checkbox input field only supports boolean variable types. A checked checkbox corresponds to
the value true
, an unchecked checkbox corresponds to the value false
.
In order to bind a <select>
box to a Java Boolean
variable, the directive
cam-variable-type="Boolean"
must be used.
Example:
<select cam-variable-name="APPROVED"
cam-variable-type="Boolean">
<option value="true">Yes</option>
<option value="false">No</option>
</select>
In order to bind a text input field to a Java Boolean
variable, the directive
cam-variable-type="Boolean"
must be used.
Text input fields of type Boolean
accept the following string values:
true
false
Meaning that the user has to type the words "true" or "false" into the text input field.
Example:
<input type="text"
cam-variable-name="IS_VIP_CUSTOMER"
cam-variable-type="Boolean" />
Select boxes are HTML controls of the form
<select></select>
A select box can be bound to a process variable using the cam-variable-name
directive:
<select cam-variable-name="foo"
cam-variable-type="String">
<option>bar</option>
<option>zar</option>
</select>
The select box supports the same value types as <input type="text">
.
The <option>
entries can be populated using a variable. The name of the variable can be provided using the cam-choices
directive:
<select cam-variable-name="PRODUCT_TYPE"
cam-variable-type="String"
cam-choices="AVAILABLE_PRODUCT_TYPES">
</select>
The directive cam-choices
expects the values to be a List or Map (Object). In case of a Map (Object), the keys of the map are used as values of the options. java.util.Map
and java.util.List
are supported but must be serialized as JSON:
Map<String, String> productTypes = new HashMap<String, String>();
productTypes.put("001", "Notebook");
productTypes.put("002", "Server");
productTypes.put("003", "Workstation");
execution.setVariable("AVAILABLE_PRODUCT_TYPES",
objectValue(customerData)
.serializationDataFormat(SerializationDataFormats.JSON)
.create());
Would be rendered as
<select cam-variable-name="PRODUCT_TYPE"
cam-variable-type="String"
cam-choices="AVAILABLE_PRODUCT_TYPES">
<option value="001">Notebook</option>
<option value="002">Server</option>
<option value="003">Workstation</option>
</select>
It is possible to use custom JavaScript in embedded forms.
Custom JavaScript can be added to a form by using a <script>
tag and adding the cam-script
directive:
<form role="form">
<script cam-script type="text/form-script">
// custom script goes here
</script>
</form>
Inside a form script, the following built-in variables and functions are available:
The camForm
variable is an instance of the CamSDK.Form
class and is the primary access point to
the form API and allows definition of event handers for participation in the form lifecycle:
<form role="form">
...
<script cam-script type="text/form-script">
var variableManager = camForm.variableManager;
camForm.on('variables-fetched', function() {
// access to all process variables after the form has loaded
console.log(variableManager.variables);
});
</script>
</form>
Only available with AngularJS integration.
Provides access to the current AngularJS scope:
<form role="form">
<input type="text"
cam-variable-name="CUSTOMER_ID"
cam-variable-type="String"
ng-model="customerId" />
<script cam-script type="text/form-script">
camForm.on('variables-applied', function() {
// the input field is bound to $scope.customerId
$scope.customerId = "some-id";
});
</script>
</form>
Only available with AngularJS integration.
<form role="form">
<script cam-script type="text/form-script">
inject([ '$scope', '$http', function($scope, $http) {
camForm.on('form-loaded', function() {
// use injected $http service for making requests
});
}]);
</script>
</form>
It is possible to participate in the lifecycle of the form. See Form Lifecycle and Events for more details.
When loading the form, the values of all variables used in the form will be fetched from the
backend. This means that the form SDK will only fetch those variables which are actually used in the
form. The most convenient way for using a variable is the cam-variable-name
directive. However,
there are some situations where directive-based usage is inconvenient. In such situations it is
useful to declare additional variables programmatically:
<form role="form">
<div id="my-container"></div>
<script cam-script type="text/form-script">
var variableManager = camForm.variableManager;
camForm.on('form-loaded', function() {
// this callback is executed *before* the variables are loaded from the server.
// if we declare a variable here, its value will be fetched as well
variableManager.fetchVariable('customVariable');
});
camForm.on('variables-fetched', function() {
// this callback is executed *after* the variables have been fetched from the server
var variableValue = variableManager.variableValue('customVariable');
$( '#my-container', camForm.formElement).textContent(variableValue);
});
</script>
</form>
Similar to fetching additional variables using a script, it is also possible to add additional variables to the submit:
<form role="form">
<script cam-script type="text/form-script">
var variableManager = camForm.variableManager;
camForm.on('submit', function() {
// this callback is executed when the form is submitted, *before* the submit request to
// the server is executed
// creating a new variable will add it to the form submit
variableManager.createVariable({
name: 'customVariable',
type: 'String',
value: 'Some Value...',
isDirty: true
});
});
</script>
</form>
The following is a small usage example which combines some of the features explained so far.
It uses custom JavaScript to implement a custom interaction with a form field which does not
use any cam-variable-*
directives.
It shows how custom scripting can be used for
<form role="form">
<!-- custom control which does not use cam-variable* directives -->
<input type="text"
class="form-control"
id="customField">
<script cam-script type="text/form-script">
var variableManager = camForm.variableManager;
var customField = $('#customField', camForm.formElement);
camForm.on('form-loaded', function() {
// fetch the variable 'customVariable'
variableManager.fetchVariable('customVariable');
});
camForm.on('variables-fetched', function() {
// value has been fetched from the backend
var value = variableManager.variableValue('customVariable');
// write the variable value to the form field
customField.val(value);
});
camForm.on('submit', function(evt) {
var fieldValue = customField.val();
var backendValue = variableManager.variable('customVariable').value;
if(fieldValue === backendValue) {
// prevent submit if value of form field was not changed
evt.submitPrevented = true;
} else {
// set value in variable manager so that it can be sent to backend
variableManager.variableValue('customVariable', fieldValue);
}
});
</script>
</form>
The above example uses jQuery for interacting with the HTML controls. If you use AngularJS, you can also populate the $scope
in the variables-fetched
callback and read the values from the $scope
in the submit
callback:
<form role="form">
<!-- custom control which does not use cam-variable* directives
but binds to the angular scope -->
<input type="text"
class="form-control"
id="customField"
ng-model="customerId">
<script cam-script type="text/form-script">
var variableManager = camForm.variableManager;
$scope.customerId = null;
camForm.on('form-loaded', function() {
// fetch the variable 'customVariable'
variableManager.fetchVariable('customVariable');
});
camForm.on('variables-fetched', function() {
// value has been fetched, bind to $scope.customerId
$scope.customerId = variableManager.variable('customVariable').value;
});
camForm.on('submit', function(evt) {
// set value in variable manager so that it can be sent to backend
variableManager.variableValue('customVariable', $scope.customerId);
});
</script>
</form>
If a form script is loaded using an XHR from a web server, it is executed using eval()
. In order
to debug it, you need to use browser-specific debugger extensions.
If you are using the Google Chrome debugger, you can add the debugger;
directive to the source
code of the script:
<form role="form">
<script cam-script type="text/form-script">
debugger;
</script>
</form>
The following examples show example scenarios of custom JavaScript in embedded forms.
This example includes an image, which is located in the contextPath of the form (i.e., in the same directory). The URL of the image is retrieved via the task form key method of the REST API:
<form role="form">
<script cam-script type="text/form-script">
inject(['$http', 'Uri', function($http, Uri) {
camForm.on('form-loaded', function() {
$http.get(Uri.appUri("engine://engine/:engine/task/" + camForm.taskId + "/form")).success(function(result){
$scope.contextPath = result.contextPath;
});
});
}]);
</script>
<img ng-src="{{contextPath}}/image.png" />
</form>
The form is parsed, and variable names are collected from the markup. This means that directives
like cam-variable-name
are evaluated and the resulting variables are declared in the
variableManager
.
Events:
form-loaded
is fired after the form has been parsed, and all form directives have been
evaluated.In the second phase, a request is made to the server to gather the values of the variables declared in the variable manager.
Events:
variables-fetched
is fired after the request returns and the values of the variables have
been merged into the variableManager.If a saved state of the form exists, the variable values are replaced with the saved state.
Events:
variables-restored
is fired after the saved values of the variables have been merged with
the values in the variableManagerNext, the variables are applied to the form controls. This means that HTML input fields and select boxes are populated with the variable values present in the variableManager.
Events:
variables-applied
is fired after the values of the variables have been applied to the
form controls.The user interacts with the form. In this phase no events are fired.
The user can save the form, which causes the current values of the variables to be stored in the localStorage. If the user comes back to the form later, the values are restored.
Events:
store
is fired before the values of the variables are written to the localStorage. An
event handler may prevent the values from being stored.variables-stored
is fired after the values are written to the localStorage.Finally, the form is submitted.
Events:
submit
is fired before the submit request is sent to the server. An event handler may
prevent the form from being submitted by setting the property submitPrevented
true.
submit-success
is fired after the server successfuly treated the submission
submit-failed
is fired after the server failed at treating the submission
or when a network error happened
Event listeners can be registered from custom JavaScript:
<form role="form" name="form">
<script cam-script type="text/form-script">
camForm.on('form-loaded', function() {
// handle form loaded
});
camForm.on('variables-fetched', function() {
// handle variables fetched
});
camForm.on('variables-restored', function() {
// handle variables restored
});
camForm.on('variables-applied', function() {
// handle variables applied
});
camForm.on('store', function(evt) {
// handle store
// may prevent the store from being executed
evt.storePrevented = true;
});
camForm.on('variables-stored', function() {
// handle variables stored
});
camForm.on('submit', function(evt) {
// handle submit
// may prevent the submit from being executed:
evt.submitPrevented = true;
});
camForm.on('submit-success', function() {
// handle submit-success
});
camForm.on('submit-error', function(evt, res) {
// handle submit-error:
var error = res[0];
});
</script>
</form>
This section explains how to work with serialized Java Objects in embedded task forms.
NOTE: Out of the box, you can only work with Java Objects which are serialized in JSON format If Java Classes are serialized using JAX-B, you need to add custom XML parsing and writing logic to the embedded form. Java Objects serialized using Java Serialization cannot be used in forms.
The Form SDK will only fetch those variables which are actually used in a form. Since a Complex Java
Object is usually not bound to a single input field, we cannot use the cam-variable-name
directive.
We thus need to fetch the variable programatically:
<script cam-script type="text/form-script">
camForm.on('form-loaded', function() {
// tell the form SDK to fetch the variable named 'invoiceData'
camForm.variableManager.fetchVariable('invoiceData');
});
camForm.on('variables-fetched', function() {
// work with the variable (bind it to the current AngularJS $scope)
$scope.invoiceData = camForm.variableManager.variableValue('invoiceData');
});
</script>
In case the variable does not yet exist (for instance in a Start Form), you have to create the variable and specify the necessary meta data in order for the process engine to correctly handle the variable as Java Object.
<script cam-script type="text/form-script">
var dataObject = $scope.dataObject = {};
camForm.on('form-loaded', function() {
// declare variable 'customerData' incuding metadata for serialization
camForm.variableManager.createVariable({
name: 'customerData',
type: 'Object',
value: dataObject,
valueInfo: {
// indicate that object is serialized as json
serializationDataFormat: 'application/json',
// provide classname of java object to map to
objectTypeName: 'org.camunda.bpm.example.CustomerData'
}
});
});
</script>
A full example of this feature can be found in the Camunda BPM Examples Repository.
This section explains how to integrate the Forms SDK into a custom HTML 5 Application. (Note: If you are using Camunda Tasklist you can skip this section. Camunda Tasklist readily integrates the Forms SDK.)
The Forms SDK library can be downloaded from Github.
Alternatively, the Forms SDK can be installed using the Bower package manager:
bower install camunda-bpm-sdk-js --save
The Forms SDK depends on the following libraries:
The Forms SDK optionally depends on the following libraries:
Next, you need to add the JavaScript Library to the page.
<script src="jquery-2.1.1.min.js" type="text/javascript"></script>
<script src="camunda-bpm-sdk.min.js" type="text/javascript"></script>
Or, with AngularJS Support:
<script src="angular.min.js" type="text/javascript"></script>
<script src="camunda-bpm-sdk-angular.js" type="text/javascript"></script>
The Forms SDK uses an instance of the CamSDK.Client
to communicate with the process engine (over the REST API):
var camClient = new CamSDK.Client({
mock: false,
apiUri: 'http://localhost:8080/engine-rest'
});
In order to create a form, you need to create an instance of CamSDK.Form
:
new CamSDK.Form({
// ...
});
In case the form is a task form (i.e., the submission of the form should trigger the completing of a task), you need to provide a taskId
:
new CamSDK.Form({
client: camClient,
// the task ID
taskId: 'someTaskId',
//...
});
In case the form is a start form (i.e., the submission of the form should trigger a new process instance to start), you need to provide a processDefinitionId
:
new CamSDK.Form({
client: camClient,
// the process definition ID
processDefinitionId: 'someProcessDefinitionId',
//...
});
The Forms SDK can automatically load forms from a URL.
The URL from which the form should be loaded is referenced using the formElement
property.
In that case you need to create a container element somewhere in the DOM:
<div id="formContainer">
</div>
And reference it in the containerElement
property when creating the CamSDK.Form
instance:
new CamSDK.Form({
client: camClient,
// URL to the form
formUrl: '/url/to/form.html',
// the task ID
taskId: 'someTaskId',
// the container to which the form should be appended. Can be a DOM element or a jQuery wrapper
containerElement: $('#formContainer'),
done: function(error, camFormInstance) {
// ..
}
});
It is also possible to initialize the Form SDK for a form already existing in the DOM.
Assuming that you have an HTML <form ...>
element present in the DOM:
<form id="myForm">
<input ....>
</form>
You can create an instance of CamSDK.Form
and attach it to the existing form like this:
new CamSDK.Form({
client: camClient,
// the task ID
taskId: 'someTaskId',
// the form element. Can be a DOM element or a jQuery wrapper
formElement: $('#myForm'),
done: function(error, camFormInstance) {
// ..
}
});
Make sure you include the AngularJS build of the Forms SDK:
<script src="angular.min.js" type="text/javascript"></script>
<script src="camunda-bpm-sdk-angular.js" type="text/javascript"></script>
Add the Forms SDK as module dependency to your application module:
angular.bootstrap(window.document, ['cam.embedded.forms', ...]);
If the form is loaded from a URL, the SDK makes sure that it is properly compiled and linked to the current Angular scope. This allows using Angular directives in forms loaded dynamically at runtime.
<form role="form" name="form">
<input type="text"
cam-variable-name="CUSTOMER_ID"
cam-variable-type="String"
ng-model="customerId">
<p ng-show="customerId">Your input: <em>{{customerId}}</em></p>
</form>
Full examples of how to integrate the Forms SDK in a custom application can be found in the Camunda BPM Examples Repository in Github.