Skip to main content

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:

Loading...

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 value true
  • {{5}} - evaluates to a number with value 5
  • {{"hello"}} - evaluates to a string with value hello
  • {{disabled}} - evaluates to the value of the variable disabled 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 value true, if the current user is a member of the clientAdvisor group

Logical operators

You can use logical operators in your expressions like not (!), or (||), and (&&), comparisons, etc.

  • {{!disabled}} - inverts the value of the disabled variable using the not operator (!)
  • {{age > 5}} - evaluates, if the value of the variable age is bigger than 5 and will return a boolean value true or false accordingly
  • {{length == 5}} - evaluates, if the value of the variable length is equal to 5 and will return a boolean value true or false accordingly
  • {{length <= 2}} - evaluates, if the value of the variable length is smaller or equal to 2 and will return a boolean value true or false accordingly
  • {{disabled || invisible}} - returns a boolean value true, if either disabled or invisible are true (or operator)
  • {{alive && kicking}} - returns a boolean value true only, if both values, alive and kicking are true (and operator)

Mathematical operators

Of course, you can also use mathematical operations.

  • {{age + 5}} - adds 5 to the value of the variable age
  • {{age - 1}} - subtracts one from the value of the variable age
  • {{age / 2}} - divides the value of the variable age by 2
  • {{age * 2}} - multiplies the value of the variable age with 2
  • {{age % 2}} - uses the modulo division of the value of the variable age and 2

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.

caution

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, so root is like a global scope and works regardless of the current scope where root is being used
  • parent - the parent scope is in relation to the current scope where the parent 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)

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

note

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.

caution

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.

note

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. To get the error messages for the complete form, or parts of the form the method flw.validate can be used.

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

NameDescriptionExample
mapAttrExtracts an attribute from every object in the listflw.mapAttr(order, 'name') = ["Macbook Pro", "Samsung 27''", "Pack keyboard + mouse"]
findGets the first object from the list with an attribute valueflw.find(order, 'category', 'laptop') = {name: "Macbook Pro", amount: 11, price: 2631.32, category: "laptop"}
findAllGets every object from the list with an attribute valueflw.findAll(order, 'category', 'peripheral') = [{ name: "Samsung 27''", ... }, { name: "Pack keyboard + mouse", ... }]
mergeConcatenates two listsflw.merge(order, extraLines) = [ { name: "Macbook Pro", ... }, { name: "Samsung 27''", ... }, { name: "Pack keyboard + mouse", ... }, { name: "Handling", ... }, { name: "Shipping", ... } ]
addAdds an element to a listflw.add(order, extraLines[0]) = [ { name: "Macbook Pro", ... }, { name: "Samsung 27''", ... }, { name: "Pack keyboard + mouse", ... }, { name: "Handling", ... } ]
remove.byAttrRemoves all elements with an attribute valueflw.remove.byAttr(order, 'category', 'peripheral') = [ { name: "Macbook Pro", ... } ]
remove.byPosRemoves the element by it's position in the listflw.remove.byPos(order, 0) = [{ name: "Samsung 27''", ... }, { name: "Pack keyboard + mouse", ... }]
remove.byObjRemoves the object from the listflw.remove.byObj(order, order[2]) = [ { name: "Macbook Pro", ... }, { name: "Samsung 27''", ... } ]
remove.nullsRemoves the null elements from the listflw.remove.nulls([0, NaN, undefined, null, "", "by"]) = [ 0, "by" ]
inReturns true if the element is in the listflw.in(flw.mapAttr(order, 'category'), 'laptop') = true
keysReturns an array of strings where each item is a key of the provided objectflw.keys(order[0]) = name,amount,price,category
valuesReturns an array where each item is a value of the provided object attributesflw.values(order[0]) = Macbook Pro,11,2631.32,laptop
exists v3.10.3+Returns true if value is not nullflw.exists(order[0]) = true
notExists v3.10.3+Returns true if value is nullflw.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 sizeflw.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

NameDescriptionExample
joinconcatenates every item with an optional separatorflw.join(flw.mapAttr(order, 'name'), '; ') = "Macbook Pro, Samsung 27''; Pack keyboard + mouse"
sumsums all valuesflw.sum(flw.mapAttr(order, 'amount')) = 26
dotProdsums the products of corresponding entriesflw.dotProd(flw.mapAttr(order, 'amount'), flw.mapAttr(order, 'price')) = 31525.11
countcounts entriesflw.count(order) = 3
avgAverage valueflw.avg(flw.mapAttr(order, 'price')) = 1019.8166666666667
maxMaximum valueflw.max(flw.mapAttr(order, 'price')) = 2631.32
minMinimum valueflw.min(flw.mapAttr(order, 'price')) = 124.01

Date-based functions

NameDescriptionExample
currentDatecurrent date without time in UTCflw.currentDate() = 2020-07-29T00:00:00.000Z
dateAddSums an amount of time (years, months, days, minutes, seconds) to the dateflw.dateAdd(flw.now(), 1, 'months') = 2020-08-29T10:10:33.654Z
dateSubtractSubtracts an amount of time (years, months, days, minutes, seconds) to the dateflw.dateSubtract(flw.now(), 1, 'months') = 2020-06-29T10:10:33.654Z
formatDate1Formats a date into a stringflw.formatDate(flw.now(), 'DD/MM/YYYY') = 29/07/2020
formatTime2Formats a date using the default time formatflw.formatTime(flw.now()) = 10:10
isAfterReturns true if the first date is after the second one. It admits a third parameter with the precission to useflw.isAfter(flw.now(), flw.currentDate()) = TRUE flw.isAfter(flw.now(), flw.currentDate(), 'day') = FALSE
isBeforeReturns true if the first date is before the second one. It admits a third parameter with the precission to useflw.isBefore(flw.currentDate(), flw.now()) = TRUE flw.isBefore(flw.currentDate(), flw.now(), 'day') = FALSE
nowcurrent date in UTCflw.now() = 2020-07-29T10:10:33.654Z
parseDateConverts a string with a format into a dateflw.parseDate('01_13_18__11_31', 'MM_DD_YY__HH_mm') = 2018-01-13T11:31:00.000Z
sameDateReturns true if the first date is the same as the second one. It admits a third parameter with the precission to useflw.sameDate(flw.now(), flw.currentDate()) = FALSE flw.sameDate(flw.now(), flw.currentDate(), 'day') = TRUE
secondsOfDaySeconds passed from the beginning of the dayflw.secondsOfDay(flw.now()) = 46354
startOfReturns the start of the date period (year, month, day)flw.startOf(flw.now(), 'month') = 2022-02-01T00:00:00.000Z

Mathematical functions

NameDescriptionExample
roundround number to n decimalsflw.round(25.2345, 2) = 25.23
floorround number downwards to the nearest integerflw.floor(26.91) = 26
ceilround number upwards to the nearest integerflw.ceil(23.2345) = 24
absgets the absolute (positive) value of a numberflw.abs(-25.5) = 25.5
parseIntParses a text value into an integerflw.parseInt("14") = 14 (as number)
parseFloatParses a text value into a numberflw.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 formatflw.numberFormat(123, "es-ES") = 123,00

String-based functions

NameDescriptionExample
encodeURIComponentEncodes special characters and characters that have special meanings in URIs like , / ? : @ & = + $ #flw.encodeURIComponent(josé@flowable.com) = jos%C3%A9%40flowable.com
encodeURIEncodes special characters and none of , / ? : @ & = + $ #flw.encodeURI('http://google.com?q=josé@flowable.com') = http://google.com?q=jos%C3%A9@flowable.com
JSON.stringifyGenerates a json string from an objectflw.JSON.stringify([1, 2]) = "[1, 2]"
JSON.parseGenerates a json object from a JSON objectflw.JSON.parse("[1, 2]") = [1, 2]
encode v3.10.0+Serializes a query object to a safe params in the URL
  • flw.encode({ q: "h", a: "b" }) = q=h&a=b
  • flw.encode({ q: "h", a: "b" }, "?") = ?q=h&a=b
  • flw.encode({ q: "h", a: "b" }, "&") = &q=h&a=b
sanitizeHtml v3.10.3+Given a HTML string cleans every possible XSS attackflw.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 stringflw.escapeHtml("<img src='x' />") = "&lt;img src=&quot;x&quot;&gt; "

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 nameDefault valueDescription
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.
actionaction-api
engageengage-api
formform-api
idmidm-api
reportplatform-api/reports
cmmncmmn-api
processprocess-api
platformplatform-api
loginauth/login
logoutauth/logout
authauth
contentcontent-api
dmndmn-api
dataobjectdataobject-api
auditaudit-api
actuatoractuator
templatetemplate-api
inspectinspect-api
note

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'
}
}
};
note

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')}}

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')}}
info

For expressions in expression buttons, a property accessors after an flw-function is not supported, for example

{{flw.getMasterDataInstance('masterDataInstanceId').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')}}

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']
}
]

  1. Format supports Moment.js string format. When it's not provided, it will use locale.timeFormat from translations file or HH:mm
  2. Format supports Moment.js string format. When it's not provided, it will use locale.dateFormat from translations file or YYYY-MM-DD
  3. Accepts locale, styles, fraction digits and currency options defined in the specification of the Intl.NumberFormat API.