Form Expressions
This is an old version of the documentation for Flowable until version 3.13 and for the Angular-based Flowable Design 3.14/3.15. If you are running the latest version of Flowable please check out the current version of this page.
Expressions are calculations that flowable forms can do for you. You can use expressions in most of the attributes used in form components. This allows you to bind components together and define form behavior.
Expressions are wrapped in double curly braces {{
and }}
and the engine will try to calculate them.
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
.
To access items in an array you can use brackets array[index]
.
{{true}}
{{5}}
{{"hello"}}
{{disabled}}
{{$currentUser.id}}
{{$currentUser.memberGroups.has('flowableUser')}}
You can use logical operators
{{!disabled}}
{{age > 5}}
{{length == 5}}
{{length <= 2}}
{{disabled || invisible}}
{{alive && kicking}}
Mathematical operators
{{age + 5}}
{{age - 1}}
{{age / 2}}
{{age * 2}}
{{age % 2}}
String concatenation
Hello {{name}} {{surname}}, have a nice {{dayphase}}
{{name + " " + surname}}
Conditionals
{{age < 16 ? 'child' : 'adult'}}
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 |>
(It executes the function on the right side passing the parameter from the left side)
{{date2 |> flw.formatTime}}
{{date2 |> flw.formatDate}}
And all of them mixed
{{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 users 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 it's a special expression that doesn't allow operators.
Most container components, like panels and subforms allow you to define a value too. This scopes all the values in the components inside the container. For example, if you define this form:
panel: value={{address}}
text: value={{name}}
text: value={{number}}
When the user fills in the name with Baker st and the number with 221B the payload will become:
address: {
name: "Baker st",
number: "221B"
}
Components can write data in the payload in their scope and their children's scope but not in their ancestors'.
To access and edit data from its ancestors' scope, components can use the special keyword $payload that provides the whole payload. See this complete example:
checkbox: label="Show surname" value={{showSurname}}
panel: value={{client}}
text: value={{name}}
text: value={{surname}} visible={{$payload.showSurname}}
In some cases is necessary to access to the parent scope.
In the following example we want to ensure children have +14yo when their families didn't contract the nursery service. To do that we use the $itemParent keyword to access the parent scope of the component.
subform: value={{families}} [[multipleEntries=true]]
boolean: label=Nursery service value={{nursery}}
subform: value={{children}} [[multipleEntries=true]]
number: label=Age value={{age}} [[min={{$itemParent.nursery ? undefined : 14}}]]
$itemParent is read-only, so it can't be used to alter parent scope variables.
$temp variables
While designing a form it's sometimes useful to use variables that we don't want to store with the payload. In the previous example, we might not want to keep the showSurname variable. So we could do:
checkbox: label="Show surname" value={{$temp.showSurname}}
panel: value={{client}}
text: value={{name}}
text: value={{surname}} visible={{$payload.$temp.showSurname}}
$temp variables are kept with the payload and can be used as any other attributes in the payload.
When integrating Flowable Forms there is a utility offered to remove the $temp
variables. Please see the developer guide for further information.
Special expression values
There are some special expression:
{{$payload}}
Root path used to access any payload value (e.g. {{$payload.mySubform.myText}}
)
{{$formValid}}
Readonly boolean value containing whether the form has errors or is valid.
Component specific
Some components provide more data that you can use, for example, $item
and $searchText
in the select component.
flw functions
There are some utility functions also available inside expressions.
In the following examples we will use data from this payload:
{
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" }
]
}
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", ... }] |
Aggregation
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
Name | Description | Example |
---|---|---|
now | current date in UTC | flw.now() = 2020-07-29T10:10:33.654Z |
currentDate | start of the current day and shifted to UTC | flw.currentDate() = 2020-07-29T00:00:00.000Z |
secondsOfDay | Seconds passed from the beginning of the day | flw.secondsOfDay(flw.now()) = 46354 |
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 |
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 |
startOf | Returns the start of the date period (year, month, day) | flw.startOf(flw.now(), 'month') = 2022-02-01T00: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 |
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 |
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 |
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 |
Math
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
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"> " |
- 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.↩