Form Expressions
Introduction
Form expressions are "evaluations" or "calculations" used in building Flowable forms. Form expressions are available for use in the majority of the attributes used in form components allowing you to bind components together and define specific form behavior.
Form expressions are defined using matching double curly braces {{
and }}
, and the payload holds the form data the user has to view or fill in.
Expressions are built using values from the payload, literals (numbers, strings and booleans) and a few special values.
For example, you can hide or disable a component when a check is checked:
Valid expressions
You can use any value from the payload, literals (numbers, strings and booleans) and (additional data) inside an expression.
To access attributes of objects you can use the dot operator object.attribute
, like {{customer.firstName}}
or even in a deeper nested data structure like
{{customer.address.city}}
.
To access items in an array you can use brackets array[index]
, like {{invoice.lineItems[5]}}
or even using another variable for the index like
{{invoice.lineItems[index]}}
.
Some examples of expressions
{{true}}
- evaluates to a boolean with valuetrue
{{5}}
- evaluates to a number with value5
{{"hello"}}
- evaluates to a string with valuehello
{{disabled}}
- evaluates to the value of the variabledisabled
in the payload (might be a boolean value){{$currentUser.id}}
- evaluates to the id of the current logged-in user (part of the supported, predefined values, see more of those later){{$currentUser.memberGroups.has('clientAdvisor')}}
- evaluates to a boolean valuetrue
, if the current user is a member of theclientAdvisor
group
Logical operators
You can use logical operators in your expressions like not (!
), or (||
), and (&&
), comparisons, etc.
{{!disabled}}
- inverts the value of thedisabled
variable using thenot
operator (!
){{age > 5}}
- evaluates, if the value of the variableage
is bigger than5
and will return a boolean valuetrue
orfalse
accordingly{{length == 5}}
- evaluates, if the value of the variablelength
is equal to5
and will return a boolean valuetrue
orfalse
accordingly{{length <= 2}}
- evaluates, if the value of the variablelength
is smaller or equal to2
and will return a boolean valuetrue
orfalse
accordingly{{disabled || invisible}}
- returns a boolean valuetrue
, if eitherdisabled
orinvisible
aretrue
(or operator){{alive && kicking}}
- returns a boolean valuetrue
only, if both values,alive
andkicking
aretrue
(and operator)
Mathematical operators
Of course, you can also use mathematical operations.
{{age + 5}}
- adds5
to the value of the variableage
{{age - 1}}
- subtracts one from the value of the variableage
{{age / 2}}
- divides the value of the variableage
by2
{{age * 2}}
- multiplies the value of the variableage
with2
{{age % 2}}
- uses the modulo division of the value of the variableage
and2
In all those examples, the actual value of the variable age
is left untouched as the expression only uses the value for calculating the result.
String concatenation
You can use expressions within strings to add values from variables (payload) into the string:
Hello {{name}} {{surname}}, have a nice {{dayphase}}
{{name + " " + surname}}
Conditional expressions
You can also build conditions with different outcomes within expressions:
{{age < 16 ? 'child' : 'adult'}}
Ecmascript
You can also use Ecmascript methods and attributes of the objects resolved in expressions. For example:
{{case.dueDate.toLocaleDateString()}}
{{tweet.length}}
{{names.join(", ")}}
{{colors.indexOf['red']}}
Pipe operator
The pipe operator |>
executes the function on the right side passing the parameter from the left side.
{{date2 |> flw.formatTime}}
{{date2 |> flw.formatDate}}
Mixing all of the above
And of course, you can also mix them all within one big frontend expression:
{{user.age + 1 == 21 && !user.admin ? "Next year you can become an admin" : user.admin ? "admin" : "go away " + user.name }}
Scope
Input components assign their value into the payload. For example, if a text component has value {{name}}
when the user writes Jordan
in that input,
the payload will get an attribute name
with value Jordan
.
The expression in the value
attribute of an input component is a special expression that doesn't allow operators! That sort of expression acts as a binding
between the input component and the payload using an expression.
Where the component adds the value into the payload is named "scoping", which is a very important concept to understand.
In a typical case- or process-oriented application, you will have several scopes, that are predefined:
root
- the root scope always addresses the top-level instance which could either be a case or process instance, soroot
is like a global scope and works regardless of the current scope whereroot
is being usedparent
- the parent scope is in relation to the current scope where theparent
scope term is used (see the following example to get an idea)self
- the self scope might come in handy when scoping is necessary within a sub-scoped panel or subform
If we assume the following execution tree:
- case instance
- process task within case (sub-process instance 1)
- call activity within process (sub-process instance 2)
- user task (the task is rendered with by its task form)
- call activity within process (sub-process instance 2)
- process task within case (sub-process instance 1)
If we use expressions on the task form, then here is what the predefined scopes mean:
self
- would lead to data stored at sub-process instance level 2 (as a task by default does not have its own scope in the UI but is merged with its process or case instance)parent
- would lead to data stored at sub-process instance level 1 (coming from the process task within the case)root
would lead to data stored at top (root) case level
Let's assume we have structured data on case level (JSON variable) named customer
, holding some information like this:
{
"customer": {
"name": "Doe",
"firstName": "John",
"address": {
"street": "Sample street",
"number": "123",
"zip": 12345,
"city": "Sample"
}
}
}
The expressions on the task form would lead to the following values:
{{root.customer.name}}
- would resolve to "Doe"{{root.customer.address.city}}
- would resolve to "Sample"
Defining a sub-scope
We can even use a sub-scope within the payload for easier value binding of the components into your structured payload. You typically do this using container components like a panel or a subform.
Assume we have a panel or sub-form on that task form where we render the address of the customer and maybe it is even a standard reusable component we use in a lot of places, where ever we render or input for an address.
You can then define the scope by checking the Store panel data into single variable
and use an expression to bind to the desired scope which in our case would
be {{root.customer.address}}
. All the components within that subform could then simply bind to the fields within the address scope. For instance the street
input field would bind to {{street}}
as it lives within the address scope, instead of {{root.customer.address.street}}
.
Using sub-scopes on sub-forms is a great way to build reusable form components as you can bind them to any sub-scope you want where ever they are used and keep the sub-form definition independent of the scope.
In the reverse sense, you can also use sub-scopes to define the structure within the payload you want to have.
As we saw with the predefined scopes like root
, parent
and self
, components can bind to data within the predefined scopes within the execution tree.
But components within scoped containers like panels, sub-forms, modals, master-details, etc have limited accessibility to their parent structure as they might
be re-used in various places where the overall data structure varies. Those components can only write data in their sub-scope and below, but not in their parent
scope.
Access parent scope within scoped containers
But sometimes you still need access to data in the parent structure from within such a scoped container and there is the $payload
keyword available to get
access to the full payload (similar as the root
prefix does, but working from everywhere within the data structure, even from within scoped containers).
To access the customer name from within the address-scoped sub-form as an example, you can use {{$payload.customer.name}}
to get access to that data field.
Or as another example, assume you have a global flag whether the data on the full form should be editable, stored at root level with name formEditable
which
should even work across all sub-scopes.
You can then use {{$payload.formEditable}}
everywhere, even in scoped containers or nested components to toggle the enabled
flag on the components.
Multiple items in a sub-form
A sub-form is a great way to create reusable form components and with the sub-scope, it even can be used in different data structures, as long as the data binding within the sub-form stays the same.
But there is more to that component as it can also be used with multiple elements resulting in a collection (array) data structure.
Assume we have an array of phone numbers on a customer data form, each record holds the phone number and its type to have a data structure like this:
{
"phoneNumbers": [
{
"number": "+1 555 123 456",
"type": "main"
},
{
"number": "+1 555 222 333",
"type": "mobile"
}
]
}
We can add a sub-form to our customer data form and scope it to {{phoneNumbers}}
and of course with Multiple elements
turned on. The input fields within
the sub-form can then bind to {{number}}
and {{type}}
respectively.
If we need some data from outside the array, like the editable flag we used before, we can use the keyword $itemParent
to gain access to the scope just
outside our multi-element sub-form (array), like {{$itemParent.isEditable}}
as an example.
The keyword $itemParent
is enabling access to the scope outside the sub-form, but it is read-only, so it can't be used to alter the parent scope data!
If data is changed (e.g. you bind a editable component to something within $itemParent
), it is creating a copy within the scoped component, so be aware of
not using that keyword this way.
If you needed to actually be able to change data outside the scoped container, use $payload
instead, which gives read and write access to that full
payload.
Sometimes you would like to render stuff on such a multi-element subform only on the first element or on every element, but the last and so forth.
For this purpose you can use the $index
keyword.
When you only want to render a component on the first element (row), you can use the expression {{$index != 0}}
for the ignored
attribute, so the
component is ignored on every row other than the first one (index 0).
Likewise, if you want to render like a horizontal line between the elements, but obviously not on the last element (row), you can use
{{$index == $itemParent.nameOfArray.length - 1}}
as the ignored
attribute, where that component would be ignored on the last element in the array.
Temporary variables
There is a very specific, predefined scope for temporary variable values. Typically you can use this scope to store values only relevant for the form rendering.
As an example, you might want to store the editable
flag we used before within the temporary scope so you can toggle it as long as you visit the form,
but that value will not be stored as it is temporary.
Values bound to the temporary scope are not saved when transmitting the form to the backend but removed before sending the payload for saving to the backend.
The keyword to access the temporary scope is $temp
. If we use the sample again with the editable flag, you could use the following expression for
the enabled
attribute: {{$temp.isEditable}}
and obviously bind the same expression to a checkbox to be able to toggle whether the form is currently
editable or not.
The $temp
scope is also very useful to store calculated values like sums or collect information from a list which you want to render somehow, but not
store as part of the payload.
Another example, showing the use of the $temp
scope for a flag whether tho show or hide the surname input field:
checkbox: label="Show surname" value={{$temp.showSurname}}
panel: value={{client}}
text: value={{name}}
text: value={{surname}} visible={{$payload.$temp.showSurname}}
All variables within the $temp scope are kept with the payload and can be used the same way as any other attributes in the payload, but they are not stored when the payload is saved in the backend.
When integrating Flowable Forms in your own UI, there is a utility offered to remove the $temp
variables.
Please see the developer guide for further information.
Special expression values or keywords
There are some specific, predefined keywords or expression values.
{{$payload}}
- as we have seen before, this keyword gives you access to the full payload of the current form, no mather in which sub-scope you are
using this keyword.
{{$formValid}}
- this read-only boolean value contains whether the form is currently valid or has some validation errors.
There are a lot of component specific keywords as well like {{$item}}
or {{$searchText}}
or as we have seen the {{$index}}
keyword.
You can look them up within the documentation of the component, as an example within the Sub-Form component documentation.
Functions within expressions
There are some functions available you can use within expressions. Some might help with formatting, others calculate something or extract data from a data structure or array.
In the following examples we assume a data structure in the payload of the following structure:
{
order: [
{ name: "Macbook Pro", amount: 11, price: 2631.32, category: "laptop" },
{ name: "Samsung 27''", amount: 4, price: 304.12, category: "peripheral" },
{ name: "Pack keyboard + mouse", amount: 11, price: 124.01, category: "peripheral" }
],
extraLines: [
{ name: "Handling", amount: 1, price: 100.0, category: "extra" },
{ name: "Shipping", amount: 1, price: 120.0, category: "extra" }
]
}
Functions based on a list
Name | Description | Example |
---|---|---|
mapAttr | Extracts an attribute from every object in the list | flw.mapAttr(order, 'name') = ["Macbook Pro", "Samsung 27''", "Pack keyboard + mouse"] |
find | Gets the first object from the list with an attribute value | flw.find(order, 'category', 'laptop') = {name: "Macbook Pro", amount: 11, price: 2631.32, category: "laptop"} |
findAll | Gets every object from the list with an attribute value | flw.findAll(order, 'category', 'peripheral') = [{ name: "Samsung 27''", ... }, { name: "Pack keyboard + mouse", ... }] |
merge | Concatenates two lists | flw.merge(order, extraLines) = [ { name: "Macbook Pro", ... }, { name: "Samsung 27''", ... }, { name: "Pack keyboard + mouse", ... }, { name: "Handling", ... }, { name: "Shipping", ... } ] |
add | Adds an element to a list | flw.add(order, extraLines[0]) = [ { name: "Macbook Pro", ... }, { name: "Samsung 27''", ... }, { name: "Pack keyboard + mouse", ... }, { name: "Handling", ... } ] |
remove.byAttr | Removes all elements with an attribute value | flw.remove.byAttr(order, 'category', 'peripheral') = [ { name: "Macbook Pro", ... } ] |
remove.byPos | Removes the element by it's position in the list | flw.remove.byPos(order, 0) = [{ name: "Samsung 27''", ... }, { name: "Pack keyboard + mouse", ... }] |
remove.byObj | Removes the object from the list | flw.remove.byObj(order, order[2]) = [ { name: "Macbook Pro", ... }, { name: "Samsung 27''", ... } ] |
remove.nulls | Removes the null elements from the list | flw.remove.nulls([0, NaN, undefined, null, "", "by"]) = [ 0, "by" ] |
in | Returns true if the element is in the list | flw.in(flw.mapAttr(order, 'category'), 'laptop') = true |
keys | Returns an array of strings where each item is a key of the provided object | flw.keys(order[0]) = name,amount,price,category |
values | Returns an array where each item is a value of the provided object attributes | flw.values(order[0]) = Macbook Pro,11,2631.32,laptop |
exists v3.10.3+ | Returns true if value is not null | flw.exists(order[0]) = true |
notExists v3.10.3+ | Returns true if value is null | flw.notExists(order[0]) = false |
forceCollectionSize v3.14+ | The function takes in three parameters: the collection itself, the desired size, and an optional element. If the collection has more elements than the specified size, it will be truncated to that length. Any elements beyond the size parameter will be removed. If the collection is shorter than the requested size, it will be padded with the optional element until it reaches the desired size | flw.forceCollectionSize(order, 2) = [{ name: "Macbook Pro", ... }, { name: "Samsung 27''", ... }] flw.forceCollectionSize(order, 5, extraLines[0]) = [{ name: "Macbook Pro", ... }, { name: "Samsung 27''", ... }, { name: "Pack keyboard + mouse", ... }, { name: "Handling", ... }, { name: "Handling", ... }] |
Aggregate data within a list
Name | Description | Example |
---|---|---|
join | concatenates every item with an optional separator | flw.join(flw.mapAttr(order, 'name'), '; ') = "Macbook Pro, Samsung 27''; Pack keyboard + mouse" |
sum | sums all values | flw.sum(flw.mapAttr(order, 'amount')) = 26 |
dotProd | sums the products of corresponding entries | flw.dotProd(flw.mapAttr(order, 'amount'), flw.mapAttr(order, 'price')) = 31525.11 |
count | counts entries | flw.count(order) = 3 |
avg | Average value | flw.avg(flw.mapAttr(order, 'price')) = 1019.8166666666667 |
max | Maximum value | flw.max(flw.mapAttr(order, 'price')) = 2631.32 |
min | Minimum value | flw.min(flw.mapAttr(order, 'price')) = 124.01 |
Date-based functions
Name | Description | Example |
---|---|---|
currentDate | current date without time in UTC | flw.currentDate() = 2020-07-29T00:00:00.000Z |
dateAdd | Sums an amount of time (years, months, days, minutes, seconds) to the date | flw.dateAdd(flw.now(), 1, 'months') = 2020-08-29T10:10:33.654Z |
dateSubtract | Subtracts an amount of time (years, months, days, minutes, seconds) to the date | flw.dateSubtract(flw.now(), 1, 'months') = 2020-06-29T10:10:33.654Z |
formatDate1 | Formats a date into a string | flw.formatDate(flw.now(), 'DD/MM/YYYY') = 29/07/2020 |
formatTime2 | Formats a date using the default time format | flw.formatTime(flw.now()) = 10:10 |
isAfter | Returns true if the first date is after the second one. It admits a third parameter with the precission to use | flw.isAfter(flw.now(), flw.currentDate()) = TRUE flw.isAfter(flw.now(), flw.currentDate(), 'day') = FALSE |
isBefore | Returns true if the first date is before the second one. It admits a third parameter with the precission to use | flw.isBefore(flw.currentDate(), flw.now()) = TRUE flw.isBefore(flw.currentDate(), flw.now(), 'day') = FALSE |
parseDate | Converts a string with a format into a date | flw.parseDate('01_13_18__11_31', 'MM_DD_YY__HH_mm') = 2018-01-13T11:31:00.000Z |
now | current date in UTC | flw.now() = 2020-07-29T10:10:33.654Z |
parseDate | Converts a string with a format into a date | flw.parseDate('01_13_18__11_31', 'MM_DD_YY__HH_mm') = 2018-01-13T11:31:00.000Z |
sameDate | Returns true if the first date is the same as the second one. It admits a third parameter with the precission to use | flw.sameDate(flw.now(), flw.currentDate()) = FALSE flw.sameDate(flw.now(), flw.currentDate(), 'day') = TRUE |
secondsOfDay | Seconds passed from the beginning of the day | flw.secondsOfDay(flw.now()) = 46354 |
startOf | Returns the start of the date period (year, month, day) | flw.startOf(flw.now(), 'month') = 2022-02-01T00:00:00.000Z |
Mathematical functions
Name | Description | Example |
---|---|---|
round | round number to n decimals | flw.round(25.2345, 2) = 25.23 |
floor | round number downwards to the nearest integer | flw.floor(26.91) = 26 |
ceil | round number upwards to the nearest integer | flw.ceil(23.2345) = 24 |
abs | gets the absolute (positive) value of a number | flw.abs(-25.5) = 25.5 |
parseInt | Parses a text value into an integer | flw.parseInt("14") = 14 (as number) |
parseFloat | Parses a text value into a number | flw.parseFloat("14.8") = 14.8 (as number) |
numberFormat3 v3.10.0+ | Parses a number value into a human-readable string, optionally given a locale and format | flw.numberFormat(123, "es-ES") = 123,00 |
String-based functions
Name | Description | Example |
---|---|---|
encodeURIComponent | Encodes special characters and characters that have special meanings in URIs like , / ? : @ & = + $ # | flw.encodeURIComponent(josé@flowable.com) = jos%C3%A9%40flowable.com |
encodeURI | Encodes special characters and none of , / ? : @ & = + $ # | flw.encodeURI('http://google.com?q=josé@flowable.com') = http://google.com?q=jos%C3%A9@flowable.com |
JSON.stringify | Generates a json string from an object | flw.JSON.stringify([1, 2]) = "[1, 2]" |
JSON.parse | Generates a json object from a JSON object | flw.JSON.parse("[1, 2]") = [1, 2] |
encode v3.10.0+ | Serializes a query object to a safe params in the URL |
|
sanitizeHtml v3.10.3+ | Given a HTML string cleans every possible XSS attack | flw.sanitizeHtml("<img src='x' onError='javascript:alert(1)' />") = "<img src='x' />" |
escapeHtml v3.10.3+ | Given a HTML string, escapes the closing brackets for displaying the HTML as string | flw.escapeHtml("<img src='x' />") = "<img src="x"> " |
Platform Expressions
In Flowable Platform there are some additional expressions and static variables that can be used.
Exposed Endpoints
In the form engine of the Flowable Platform UI, the endpoints can be retrieved with the following expression: {{endpoints.xxx}}
.
This variable is a dictionary object of the endpoints available to the forms inside expressions.
Endpoint name | Default value | Description |
---|---|---|
baseUrl | (undefined) | When only the baseUrl is configured, then the rest of endpoints are built based on the baseUrl as the url prefix. Otherwise, their configured value will be used as is. |
action | action-api | |
engage | engage-api | |
form | form-api | |
idm | idm-api | |
report | platform-api/reports | |
cmmn | cmmn-api | |
process | process-api | |
platform | platform-api | |
login | auth/login | |
logout | auth/logout | |
auth | auth | |
content | content-api | |
dmn | dmn-api | |
dataobject | dataobject-api | |
audit | audit-api | |
actuator | actuator | |
template | template-api | |
inspect | inspect-api |
For Flowable Work and Engage the value of all these endpoints can be configured manually through an application.properties
change at the backend,
for example with: flowable.frontend.endpoints.baseUrl = /flowable-work
.
Furthermore, this dictionary of endpoints can be enhanced in a customized frontend through the additionalData.endpoints
object, which you can extend in your src/index.tsx
as follows:
export default {
additionalData: {
endpoints: (defaultEndpoints) => {
...defaultEndpoints,
myCustomExternalApi: 'http://custom-api/whatever'
}
}
};
Alternatively, when using Flowable Work or Engage, you can define new endpoints available to the frontend through a Spring application property like:
flowable.frontend.endpoints.myCustomExternalApi=http://custom-api/whatever
If baseUrl
is set to /flowable-work
and no additional endpoint is configured, then the endpoints
object looks like:
"endpoints": {
"baseUrl": "/flowable-work",
"action": "/flowable-work/action-api",
"engage": "/flowable-work/engage-api",
"form": "/flowable-work/form-api",
"idm": "/flowable-work/idm-api",
"report": "/flowable-work/platform-api/reports",
"cmmn": "/flowable-work/cmmn-api",
"process": "/flowable-work/process-api",
"platform": "/flowable-work/platform-api",
"login": "/flowable-work/auth/login",
"logout": "/flowable-work/auth/logout",
"auth": "/flowable-work/auth",
"content": "/flowable-work/content-api",
"dmn": "/flowable-work/dmn-api",
"dataobject": "/flowable-work/dataobject-api",
"audit": "/flowable-work/platform-api",
"actuator": "/flowable-work/actuator",
"template": "/flowable-work/template-api",
"inspect": "/flowable-work/inspect-api"
}
The endpoints
dictionary is particularly useful when modelling urls of calls to the backend. For example, with the following datatable query URL value:
{{endpoints.platform}}/search/work-instances?size={{$pageSize}}&start={{$start}}&{{$sort}}&{{$filter}}
As shown above, when configuring baseUrl
to /flowable-work
, the {{endpoints.platform}}
will resolve into /flowable-work/platform-api
,
and, for the previous example, the network request to fetch the data will use a query url like:
http://your-server-hostname/flowable-work/platform-api/search/work-instances?size=10&start=0
But if the resolved query URL does not start with /
or http|s://
, for example if it resolves to platform-api/search/work-instances?size=10&start=0
then your server context (location.pathname
) will be used instead.
As an example if your Tomcat root context is work
, then the resulting query url to fetch the data will look like:
http://your-server-hostname/work/platform-api/search/work-instances?size=10&start=0
or if there is no root context:
http://your-server-hostname/platform-api/search/work-instances?size=10&start=0
Current User Information
The variable $currentUser
in the Flowable Work frontend contains information about the currently logged-in user.
$currentUser: {
id: "sherlock.holmes",
firstName: "Sherlock",
lastName: "Holmes",
displayName: "Sherlock Holmes",
email: "curator@sherlock-holmes.co.uk",
...
}
Assume you want to render the name of the current user within a text display component as an example.
You can then use {{$currentUser.displayName}}
within any text to render the name of the currently logged-in user.
It can be enhanced in a customized frontend through the additionalData.$currentUser
object, which you can extend in your src/index.tsx
as follows:
export default {
additionalData: {
$currentUser: (currentUser) => {
var userInitials =
currentUser.firstName.charAt(0).toUpperCase() +
currentUser.lastName.charAt(0).toUpperCase();
return {
...currentUser,
initials: userInitials,
canInvestigate: () => currentUser.id == "sherlock.holmes",
};
},
},
};
Retrieval of User Information
This expression is a new feature introduced as of Flowable Work version v3.10.0+ in which you can retrieve the information of any user given its id.
Internally, this makes an API call to retrieve the whole user object, so you can use any property inside the user retrieved.
For example:
// Retrieve the user information for a given userId.
{{flw.getUser('myUserId').displayName}}
This expression under the hood is built with a performant cache, which means that if you call in the same page the same expression with the same arguments, will request to the backend only a single time the resource.
Usually this concept is a common functionality to transform a userId
to any other attribute of the user, e.g. the Display Name, when showing it into
a data table row. Therefore, the Data component section is filled with a htmlComponent.
For example:
{
"type": "htmlComponent",
"value": "<span>{{flw.getUser($item.userId).displayName}}</span>"
}
Retrieval of Master Instances
The getMasterDataInstance
expression is a new feature introduced as of Flowable Work version v3.10.0+ in which you can
retrieve the information of any master data instance given its id.
{{flw.getMasterDataInstance('masterDataInstanceId').name}}
The getMasterDataInstanceByKey
expression is a new feature introduced as of Flowable Work version v3.14.1+ in which you can
retrieve the information of any master data instance given its masterDataInstanceKey and the masterDataDefinitionKey.
// Retrieval of the master data instance object based on masterDataInstanceKey and masterDataDefinitionKey.
{{flw.getMasterDataInstanceByKey('masterDataInstanceKey', 'masterDataDefinitionKey').name}}
Retrieve Data Object Instances
This expression is a new feature introduced as of Flowable Work version v3.11.7+ in which you can retrieve the information of any data object instance.
// Retrieval of the data object instance based on dataObjectDefinitionKey, dataObjectOperationKey, dataObjectLookupKey and dataObjectLookupValue
{{flw.getDataObjectInstance('dataObjectDefinitionKey', 'dataObjectOperationKey', 'dataObjectLookupKey', 'dataObjectLookupValue').variableOne}}
Validation and Validation Errors
This expression is a new feature introduced in Flowable Work version v3.12.0+ and when called will trigger a validation of the component with the id passed as parameter (if undefined will trigger a validation of the entire form) and return an array of validation errors:
// validation triggered only for one component, where the id matches to 'componentId'
{{flw.validate('componentId')}}
// validation for the entire form
{{flw.validate()}}
[
{
$computedPath: ".extraSettings.layoutDefinition.rows.0.cols.0",
$errors: ['invalidFiles'],
id: "attachment1",
validationMessages: ['There are invalid files attached.']
},
{
$computedPath: ".extraSettings.layoutDefinition.rows.1.cols.0",
$errors: ['maxFiles'],
id: "attachment2",
validationMessages: ['The number of files that can be added is 2']
},
{
$computedPath: ".extraSettings.layoutDefinition.rows.2.cols.0",
$errors: ['isRequired'],
id: "text1",
validationMessages: ['Field must not be empty']
}
]
- Format supports Moment.js string format. When it's not provided, it will use
locale.timeFormat
from translations file orHH:mm
↩ - Format supports Moment.js string format. When it's not provided, it will use
locale.dateFormat
from translations file orYYYY-MM-DD
↩ - Accepts locale, styles, fraction digits and currency options defined in the specification of the Intl.NumberFormat API.↩