Introduction to Maestro Business Rules

   MaestroThe UI design product.  |  Form Builder |  Platform Developer

Maestro allows you to create various business rules in the Code View or in the Rule Editor accessed from the Rules section of the Properties pane. Before you start creating your own business rules, you need to get some basic understanding about this concept and specifics pertaining to the Maestro product.

Values in Scope within a Maestro Rule Function

All rule functions can access the current data value of the field to which they are attached using the variable value (see Shortcuts). In addition to that, they receive several other parameters, injected references and shortcut variables as described below.

Statements vs Expression

All Maestro business rules get packaged within a function body, providing various parameters and variables described further below. The returned value from this function is the result of the rule. So, for example the following validation rule would result in a Boolean true (meaning validation passed) or an error message string depending on the value of the field.

if (value > 17) {
    return true;
} else {
    return "Not old enough";
}

However, for a rule that only returns one calculation, you can enter a simple expression and omit the return keyword. It will be added into the generated function automatically. So the same rule could be expressed simply as follows:

value > 17 ? true : "Not old enough";

Due to the semantics of the JavaScript OR (||) operator, this can be simplified further:

value > 17 || "Not old enough";

The general rule of thumb is that if the result can be expressed as a simple calculation then it doesn't need the return keyword. If it requires control flow, nested functions or any other kind of statements then you should use return where appropriate.

Parameters

Maestro business rule functions receive three parameters:

  • data – The data context that the rule operates in. For items that are not in a repeat, this will be the main form data context. Each instance of a repeat creates a new data context. Instances of nested repeats have a separate data context within the data context of their parent repeat, and so on. This can be used to look up other items of data, in the same context, or in parent, sibling or child contexts.
  • item – The view item that this rule is attached to. This can be used for looking up properties of the item.
  • info – An extra parameter that some component-specific rules use to supply additional information. For example, the Page Controller component Change rule has an info parameter with from and to properties that identify the pages being navigated from and to. To inspect the value of the info parameter for a specific rule in the JavaScript console add the following line:
    console.log(info);

As explained above, a new data context is created for each instance of a repeat. To navigate to the parent context use data.$p. To navigate to the repeat item's own data entry in the parent context (which will be an array of all the data contexts in that repeat) use data.$r. To find out a data context's index within a repeat, use data.$i.

So, to get to a sibling context - say the previous instance in a repeat - you would first navigate to the repeat's data entry and then use square-brackets to address the relevant child context in the array

data.$r[data.$i - 1];

If the item for which the rule is defined is in a container, you can access that container item using item.$parent.

Within any given container item, the child items are held in an array of rows, each of which is an array of child items within that row. So the first child of a containers first row would be item.rows[0][0].

In this way you can navigate up, down and across the view hierarchy. However, to get to a specific item it's easier to look it up in the flattened map held in Form.items, such as

Form.items.myRadioButton;

The properties of each item are available on the key properties. So to get the placeholder text of a Text Field with ID firstName you would use

Form.items.firstName.properties.placeholder;

You can easily get an ID of the current page by using the Form.getCurrentPageId() API or the property (where the page controller stores there a name of a current page) maestro.Form.$Pages.currentPage.id. To get a current name of a Modal page, use maestro.Form.pageName that returns a valid String only when a Modal page is displayed. For a Dialog, use maestro.Form.dialogName returns either be an empty String when no Dialog is shown, or the Dialog name when a Dialog is shown. You can use the following script to simplify obtaining a current page ID:

function customGetCurrentPageId() {
  if(maestro.Form.pageName === "") {
    // Non-modal page
    return maestro.Form.getCurrentPageId();
  } else {
    // Modal page
    return maestro.Form.pageName;
  }
}

Global API Objects

In the previous section we saw the a reference to an object called Form - in that case to access its items property, containing an ID-keyed map of all the view items. There are several of these global objects. (They are not global in the sense of being available on the window object, but they are injected into the scope of each business rule function using Angular.) These objects (or "Services" in the Angular terminology) are described below. A full reference of API methods available on each of them can be found using the API Methods Reference link in the Code View.

$rootScope

The Angular $rootScope object - can be useful if you are an Angular expert. See the Angular documentation.

$q

The Angular $q object. This can be used for creating and aggregating Promises. See the Angular documentation, as well as general documentation about JavaScript Promises.

$timeout

The Angular timeout function. This is similar to JavaScript's standard setTimeout function, but ensures that any data updates are rendered again in the form. Also, it can be used with a callback, as follows:

$timeout(function() {
    data.status = "Success";
}, 5000);

or it can be used to return a prommise, as follows:

$timeout(5000).then(function() {
    data.status = "Success";
});

Form

The Form service contains a number of properties and methods for general form interactions such as validation and submission.

Calc

The Calc service provides some useful methods for running calculations on data.

Resource

The Resource service provides methods for working with external resources, such as formatting image URLs.

Util

The Util service provides general utilities to assist with tasks such as searching and sorting arrays, walking the row/child view hierarchy and so on.

DynamicData

The DynamicData service provides methods for interacting with Dynamic Data Services published in Transact Manager.

Scroll

The Scroll service provides a way to scroll to and focus on particular form fields.

Insights

The Insights services enables rules to register events with Transact Insights analytics.

Translation

The Translation service has some helper methods for working with multi-lingual forms, although for basic access to localized terminology use the T shortcut described further below.

Other Shortcut Variables

value

This is a shortcut to the current data value for the field to which the rule is attached. So for a rule on a Text Input with ID lastName it is the equivalent of using data.lastName.

Rules

In the final published form, all the business rules are contained within a single Rules service. This shortcut lets you navigate to and even run business rules from within other business rules.

Business rule functions have names formatted as follows:

<rule-type>_<item-id></item-id></rule-type>

Basic standard rule types are ok (validation), us (editability), sh (visibility) and eq (calculation). Other action rule types have their full action name, such as click, blur and change. Components can also provide their own custom rule types.

So, the click rule on a button with ID verify could be accessed with

Rules.click_verify;

and the validation rule for a Decimal Field with ID age would be

Rules.ok_age;

Whilst you could call the age validation rule from another rule in the same data context with the code

Rules.ok_age(data, Form.items.age);

It would only return the validation state of that field. To properly validate the field, including display of approriate errors and setting form validation state, you should use the validation methods available in the Form service object.

T

This is the hierarchical map of all terminology items for the currently selected language in a multi-lingual form. So, to get the translated placeholder text for an item with ID firstName you could use

T.firstName.properties.placeholder;

SystemProfile

This is a shortcut to the SystemProfile node of the form's data, which contains metadata about the submission provided by Transact Manager.

Job

This is a shortcut to the Job section of the forms SystemProfile data. Useful for some collaboration job scenarios.

When the rule is triggered, the value of the field is passed in as a value variable. For instance, the Select Language component defines a Change rule as Translation.fetch(value).catch(function(error) { alert("Cannot find language code '"+value+"' in form")}). So, when you select a language from the dropdown list, the value variable will be the value of the entry you have selected.

The rule execution context also exposes the following objects, which you can call via API methods:

  • Form
  • Util
  • Calc
  • DynamicData

The Rule editor provides a quick access to the API documentation, so click API Methods to open Maestro API Reference documentation in a new tab.

While you are writing your business rules, you should keep in mind the following suggestions:

The Rule editor

Sometimes, the Rule editor can become confused about exactly what you are trying to achieve, and can interpret what you are typing in the wrong way. If you are getting unexpected results, you may want to switch to Source mode, and review the underlying HTML. You may have accidentally injected HTML tags you didn't expect.

Test often

When you are developing rule's formulae, test your work often. Several types of errors can result in your form not displaying correctly in Preview mode. The more often you test, the more likely you are to find the bit of code that is causing the problem.

Use a field within a calculation

A calculation is assumed to be code, and therefore everything is interpreted as JavaScript. When you insert a field into a calculation, simply reference the field id as data.firstname.

If you need to include text, include it in single or double quotes.

For example:

"Welcome " + data.firstname

Insert a field into text

A caption or display field is interpreted as text, and so you need special syntax in order to have everything interpreted as code. Maestro uses double curly braces to inject code into the string.

For example, to insert a field value into a piece of text, such as a caption or a display field, use the following notation:

{{ data.firstname }}

You can perform calculations within the braces, like this:

{{ data.firstname + " " + data.lastname }}

The braces mean: "Treat whatever is inside the braces as a calculation rather than as text.

Hide text display until all dependent values are valid

Sometimes a text display can look strange if only some of the values that are used within it have been completed. To avoid this, simply create a Visibility rule that looks something like this:

data.firstname != "" && data.lastname != ""

or

+data.term != 0 && +data.frequency != 0

Treating field values as numbers

All data is inherently treated as text. If you want to force a field's value to be treated as a number, prefix it with a "+". (If you don't do this, when you try to add two numbers, they will be concatenated as strings rather than added together. You can also use JavaScript functions to format the result. For example:

(+data.term * +data.frequency).toFixed(2)

or

(+data.term) + (+data.frequency).toFixed(2)

Treating field values as boolean (true/false)

All data is inherently treated as text. If you want to force a field's value to be treated as a boolean or logical value, prefix it with !!. For example:

if (!!data.is_married) ...

Conditional values

If you have, for example, some values in a dropdown list, and want to turn these into more human readable values, you could use an if statement or a switch statement. However, if you want to embed the logic inside some text, you can use the following object key lookup syntax:

{{ {'52': 'per week', '24': 'bi-monthly', '12': 'per month'} [data.frequency] }}

This is a shortcut for:

Depending on the values of [data.frequency], use the following lookup values:

  • '52' = 'per week'
  • '24' = 'bi-monthly'
  • '12' = 'per month'

This defines an in-line JavaScript object, and then uses the field's data value to index into that object. More information is available at this website.

Note that if there is no match, then the first pair will be used, and so this can be used as a default.

Next, learn how to create a rule.