Expressions and binding mechanisms

Overview

Expressions allow developers to access dynamic data, make some simple calculations, or render content.

Symbols and usage

Expressions are used widely in the JSON Definition components.

In the JSON Definition file, there will be some properties like:

title, text, {xxx}Title, {xxx}Text These properties are treated as a localized expression keys and are mainly used for display purposes.
sourceExpression, valueExpression Mainly used for data binding, for example, binding data for INSERT and UPDATE operations or getting a value from a list. Properties with a suffix of “Expression“ are data expressions.

Supported operators

Operator Description
== Equals
> Greater than
>= Greater than or equal to
< Less than
<= Less than or equal to
!= Does not equal
&& And
"
(…) Group of operators

Binding mechanism

When an expression is set to a component, all of the variables will be bound to the component. This means that when data is changed, it notifies all components that subscribe to the properties that have changed.

For example, consider a Text component:

{
  "type": "text",
  "text": "${formData.name}"
}

Localized expressions

A localized expression is an expression mainly used for display. For example:

{
    "type": "titleAndCaption",
    "title": "formA.localizedKey",
    "caption": "fformA.localizedKey2"
}

Where formA.localizedA is a key from the localization files (for example, en.json, fr.json etc.), which are used for to support multiple languages. Based on how many languages your mobile extension supports, formA.localizedA will be different for each language.

For example, if my extension supports English and French, then there will be two corresponding resources:

en.json

{
  "formA": {
    "localizedKey": "Hello ${formData.profile.name}"
  }
}

fr.json

{
  "formA": {
    "localizedKey": "Bonjour ${formData.profile.name}"
  }
}

From this example, you can see that en.json and fr.json have different text values (due to them being in different languages), but they use the same variable: formData.profile.name to render the user’s profile name. You can use data binding in your language definition files to render them as variables. All variable scope is nested inside ${…}.

Data-binding expression

Unlike localized expressions, data-binding expressions are used for data interactions, such as for inserting or updating data, or determining how to get specific data for a component from a JSON definition. All data-binding expressions have the suffix: “Expression“.

For example, consider the sourceExpression on a list component, where the user can guide the component on how to get the list of data:

{
  "type": "list",
  "sourceExpression": "formData.notes"
}

Or, use valueExpression to make a two-way data binding to a text field. The user can then edit the text and it will reflect the data back:

{
  "type": "textEditor"
  "valueExpression": "pageData.description",
}

Conditional and branching binding expressions

Unlike other expressions, where you have to put a single value or render a localized key, branching binding expressions allow you to render different values based on different conditions.

For example, consider the following UI:

A mobile phone showing the Skedulo mobile app with status badges rendering in different colors.

In this scenario, we have to render the “tag” in a different color depending on the ticket status value. There are two ways to do this:

  1. Use the branching binding expression.
  2. Use a custom function.

Branching binding expressions are supported in our UI definition and can be defined like this:

"items": [
{
  "text": "TicketListPage.ItemStatus",
  "themeValueExpression": [
    {
      "condition": "item.Status == 'Ticket Approved'",
      "value": "success"
    },
    {
      "condition": "item.Status == 'Submitted'",
      "value": "primary"
    },
    {
      "value": "none"
    }
  ]
}

As you can see in the themeValueExpression, it’s not a normal string expression, but rather an array object, which can have the following properties:

  • condition: The condition to check, this is a Data Expression that should return a boolean value

  • value: The Data Expression value, if the condition is met, this value will be returned

From the example above, when themeValueExpression is asked to return a value, it will run the following checks:

if condition == ‘Ticket Approved', return the data expression as 'success’.

if condition == ‘Submitted', return the data expression as 'primary'.

Otherwise, return the value as ’none’ and do not display any tags.

Non-branching logic

If you don’t need the branching logic, you can always define something like this instead:

"themeValueExpression": "success"

This will always get the success theme for themeValueExpression.

Note: This does not apply for all of the properties for the components we’re currently supporting, only on some special properties.

Use regular expressions

When building mobile extensions, we often need to use regular expressions (RegEx) to validate input values against specific patterns or formats. For example, emails, phone numbers, passwords, or other custom formats.

In order to use RegEx, we support a function called isRegexValid:

isRegexValid(stringToValidate: string, regexKey: string): boolean

Parameters

  • stringToValidate (Type: string): The string that needs validation against the predefined regular expression.

  • regexKey (Type: string): A key reference to the predefined regular expression in the regex.js file.

regex.js file

You need to define your regex in the “regex.js” file, which is located at mex_definition/static_resources/

const regex = {
    emailRegex: /^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/,
    urlRegex: /^(http'?):\/\/[^\s/$.?#].[^\s]*$/i,
    ....
}

Usage:

"validator": [
  {
    "type": "expression",
    "expression": "isRegexValid(pageData.EmailUser, 'emailRegex')",
    "errorMessage": "form.InvalidEmail"
  }
]
A mobile phone showing the Skedulo mobile app email address field validation.

Error handling

If the RegEx key provided does not correspond to any predefined regular expression in the regex.js file, this function will throw an error.

Complex computation using custom functions

In some scenarios, we need to use complex logic to render text based on the current data we have. Doing this with an expression alone is tricky, and sometimes not even possible. However, using a custom function can achieve this complexity.

For example, consider an extension that has three rows, each with a price value and a commission percentage value in it. You need a total of the price and the total value of the commission summed across the rows.

The data from the backend would consist of two fields:

  • Price: Float
  • Commission Float (0 → 1.0)

To achieve the sum of the three prices and the sum of the commission for each, we can use a custom function.

The first step is to define which localized key that we should use to render the Footer.

"footerText": "form.FooterContent" // The localized key

The FooterContent will look like this:

{
  "form": {
    "FooterContent": "Total: ${cf.calculateTotal(formData)}\nCommission:${cf.calculateCommission(formData)}"
  }
}

As you can see, in order to render the totals, we use cf.calculateTotal(formData) and cf.calculateCommission(formData):

  • cf. - which is the prefix stand for “custom function“, all of your custom functions that you built can be access via this prefix.

  • calculateTotal / calculateCommission - the name of the custom function you built.

  • (formData) - passes the formData object as an argument to calculateTotal.

And this is how the calculateTotal function will look in Typescript:

function calculateTotal(formData:any) { 
    var total = 0
    formData.JobProducts.forEach((item, index) => {
        total += item.Price
    })
    return total // Get the total Price
}
function calculateCommission(formData:any) { 
    var total = 0
    formData.JobProducts.forEach((item, index) => {
        total += item.Price * item.Commission
    })
    return total // Get the total Commission
}

Async expression

This expression is only used in online mode. To understand more about online mode, please see the online mode documentation.

The mobile extension engine will call the API defined in the query property, and then execute the expression property with the data context and the response returned from the query.

In metadata.json:

"mandatoryExpression": {
  "expression": "cf.checkIsDone(query.response, pageData)",
  "query": {
    "type": "standard",
    "fields": [
      "UID",
      "Name"
      ... // other fields
    ],
    "objectName": "Jobs",
    "isSingle": true,
    "filter": "UID == '${metadata.contextObjectId}'"
  }
}