Overview

This page lists some examples and methods that can be used in the Calculated Fields of the Layout Designer.

Calculated property configuration has 2 modes:

  • Assign Model: allows assigning the value of the calculated property from any other property that you can select from the Data Model;
  • Advanced Mode: allows adding related Data Model properties and localizable resources and modifying the value of the calculated property with the JavaScript Expression.

Calculated Fields are one of the Data Model property types. For more details, see the Data Model page.

 

Advanced Mode

The variables and methods that can be used in the Expression (JavaScript) section are listed in the Advanced Mode tab of the calculated property configuration:

  • + Add: click add button to add related data model properties and localizable resources.
  • Variable Name: the name of the variable that can be used in the Expression (JavaScript) section for the edited calculated field. Modify the name as necessary. 
  • Watchable: defines whether the added to the calculated field property is watchable. Possible values:
    • Selected (default): every time the watchable property value changes the calculated field value is recalculated as well;
    • Disabled: the value of the used property is the value assigned on the initial upload of the page. If the value of the added property changes the calculated field value is not recalculated. This option can be used in case you need to change another property from the current calculated property: for this case, you still add the property you are going to change to the current calculated property, set Watchable to disabled for the added property, and use it in the Expression (JavaScript) section:
  • Property Info: path to the manually added variable in the Data Model.
  • Expression (JavaScript): this configuration area allows modifying the initial value of the calculated field and defines the actual value of the calculated property depending on the added conditions and related data model properties used in the expression. The expression is constructed according to the hints described below.

Choosing a Language: EditMode

Under the hood, every calculated property has an EditMode that determines which language the expression is actually written in. Advanced Mode in the designer always produces JavaScript (modes 1–2); modes 3–5 exist for server-side properties and email filters/sorting, and are configured elsewhere, but it helps to see the whole picture:

Mode Name Language Runs On
1 simple JavaScript Browser
2 clientProperty JavaScript Browser
3 serverProperty T-SQL attribute path / SQL expression Server
4 emailFilterProperty SQL WHERE clause Server
5 emailOrderProperty SQL ORDER BY clause Server

Warning: Mode-3 properties expect T-SQL, not JavaScript — no return, no var, no arrow functions. Writing JavaScript into a mode-3 property doesn't fail loudly; it just produces a broken column. Everything below covers modes 1 and 2, the JavaScript ones.

 

Mode 3 example, for contrast (server-side, not JavaScript):

Title                                        -- a sibling attribute
T(SPSActivityClassBase).ClosedDate           -- cast, then read
AssetAffected.T(SPSComputerClassBase).Name   -- relation, then cast
LastName + ' ' + isNull(FirstName, '')       -- T-SQL expression
CASE WHEN Queue IS NOT NULL THEN 1 ELSE 0 END

The JavaScript Contract

An Advanced Mode expression isn't a standalone script, it's spliced into a function body that gets eval'd once and cached:

(function execute(r, p, v, c, t, u) {
    var w                    = window;
    var $originalValue       = v.defaultValue;
    var $oldValue            = v.oldValue;
    var $value               = v.value;
    var $config              = c;
    var $forceValueChanged   = function() { v.forceChanged = true;  };
    var $preventValueChanged = function() { v.forceChanged = false; };
    var $triggeredBy         = t;
    var $user                = u;
    var $currentThemeColors  = mx.currentThemeColors;
    /* …one var per declared parameter… */
    var $format = function() { /* … */ };

    YOUR EXPRESSION GOES HERE

})

Three consequences follow directly from this:

  1. You must return a value on every code path. Falling off the end yields undefined, and the property silently becomes undefined — no error, no warning. This is one of the most common mistakes in calculated properties.
    return messageType.$value === 1;    // ✓ correct
    return messageType;                 // ✗ returns an object, not a boolean
  2. All identifiers share one scope — every built-in $… name and every parameter's Variable Name lives in the same function body. Don't name a parameter $value.
  3. The default expression is return $value; — the identity function. Leaving it as-is is fine: an identity property is just a plain field other expressions can watch.

Recalculation Limits and Guard Idioms

Every calculated property is a getter/setter under the hood. Setting it — or any Watchable parameter changing — runs the expression, compares the result to the old value, and notifies listeners only if the value actually changed. (Dates compare by ISO string; everything else by !== plus deep equality.)

Hard ceiling: 50 recalculations per property. Cross it and you get, verbatim: Maximum relcalculations exeeded: <path>. Last call triggered by: <path> — the trigger it names is almost always your cycle. (The typos are in the product itself, not a transcription error here.)

A few guard idioms are useful for breaking a cycle before you hit that ceiling — beyond setting Watchable to Disabled as described above:

// Suppress a stale value mid-change:
return dataQueryObjId.$hasChanges ? null : $value;
return columns.$hasChanges ? $originalValue : $value;

// Only on the very first evaluation — nothing has moved yet:
return ($originalValue === $oldValue && $oldValue === $value) ? true : $value;

// React to one specific trigger only:
if ($triggeredBy === 'SubmitData.data.RecipientId') {
    $forceValueChanged();
}
return $value;

// Clear a dependent field:
if ($value) {
    validUntil.$setValue(null);
}
return $value;

Expression API

$originalValue

The first value of the current property, in other words, the value which was retrieved on the initial load of the layout.

$oldValue

Previous value, i.e. the value of the current property before the last change. Equals to $value if no direct changes were made.

$value

The current value of the calculated property. If any other property uses this calculated field it will refer exactly to this value.

The rest of the $ vocabulary

Beyond the three value variables above, the same function scope exposes:

Variable Meaning
$config The widget's configuration object
$triggeredBy Data Model path of the property whose change caused this run
$user The current user, e.g. $user.currency, $user.language.lcid
$currentThemeColors Array of {Key, Value} theme colors
$sourceState Load state of the owning data source — see below

Sources and $sourceState

Sources live in the widget's Sources array. If your expression reads from one, check $sourceState before trusting the data — it may not have loaded yet:

NOT_SET = -1 INITIAL = 0 LOADING = 1 LOADED = 2 REJECTED = 3 LOAD_REQUESTED = 4
return $sourceState.$value === 2; // LOADED = 2

Gotcha: it's easy to check $sourceState.$value === 3 and assume it means "loaded" — but 3 is REJECTED, not loaded. Always check against the enum above, not a remembered number.

 

Types and Default Values

The property's declared Type sets its default value. Return the wrong shape and the field just reads empty rather than throwing:

Type Default Value
IntType 0
StringType ''
BoolType false
GuidType undefined
DateType null
ObjectType {}
Currency { CC: '', Value: null }
Any type with IsArray: true []

Currency is a shape, not a number. Always return an object with CC (currency code) and Value (numeric amount):

return { CC: $user.currency, Value: number.$value };

Arrays change in place. Arrays are DataModelArray instances — when you return a new array, the engine calls .set() on the existing instance rather than replacing the reference, so mutating $value with push/splice also works and fires notifications:

// Build an array of n blanks:
var arr = [];
for (var c = 0; c < bCount.$value; c++) {
    arr.push({});
}
return arr;

Parameters API

Instead of param use the Variable Name of the related data model property that you added to your calculated property configuration.

param.$value

The current value of the parameter.

param.$oldValue

Value of the parameter before the current change. Equals to param.$value if no direct changes were made.

param.$hasChanges

Identifies if the parameter value has been changed in the curing iteration, i.e. recalculated due to the changes in the watched property. Is handy in case there is more than one watchable variable and you need to recalculate the value of the calculated property only if a specific property has changed. 

The Most Common Mistake, and param.$setValue()

A parameter is always an object, never a raw value — you cannot reach across the Data Model with a path string inside an expression, only through a declared parameter. One of the most common mistakes is comparing that object directly instead of reading .$value off it:

return messageType.$value === 1;   // ✓ correct — read .$value
return messageType === 1;          // ✗ compares the object itself — always false

A common pattern is simply forwarding a parameter's value, for example return objectIds.$value; or return id.$value;.

A parameter also exposes param.$setValue(v) to write to another property — this is the mechanism behind the PDRConfigurationProjectClassChange example under Helpers below.

Localization Parameters

A parameter with __type: 2 is a localized string, not a Data Model path. Its path is an $LS$[guid] reference, and the engine treats it as a plain string constant:

{ "varName": "corporate", "path": "$LS$[9587ead5-…]",
  "name": "localStr1", "__type": 2, "value": "Local:{Corporate}" }

// then, in the expression:
return $format(corporate);
return $format("{code} ({name})");         // fills from sibling parameters
return $format(message, name.$value);      // positional {0}, {1} …

Helpers

$forceValueChanged()

Call this function to force all dependent data model properties to be recalculated.

The method is used to run Service Operation when a certain condition is met. Note that the Service Operation should be configured to run on data update.

For example, open Administration application → User Interface → Control Descriptors → click OAuth2 Authentication Control → run Change Layout action to open the Layout Designer. The Data Model has the following redirectUrl property that uses $forceValueChanged() method:

When $inactive is set to false (==active) it triggers the $forceValueChanged() method, which triggers running the entire Service Operation, in this example it’s GetOAuth2AuthorizationUrl.

According to the Source Execution Mode in the Service Configuration, if any of the properties has changed, the Service Operation runs:

In order not to run the Service Operation every time when any of the properties has changed, but only when all necessary properties are available the following logic takes place.

When the Control Descriptor is added to the Dialog, it has all necessary properties as listed in the Widget Attributes. When the properties that are necessary for the $inactive are filled out, its value becomes false:

In this way, the Service Operation gets all necessary for running data and then the $forceValueChanged() method is run.

$preventValueChanged()

Call this function to prevent recalculation of all dependent properties and sources.

The usage of the method is similar to watchable property set to false. In the case of the method, it prevents calls to all dependent calculated fields.

For instance, there are two calculated fields, the value of one of them is changed by the other. 

For example, open Administration application → User Interface → Layouts → Dialogs → click Configuration Project Dialog → run Change Layout action to open the Layout Designer. The Data Model has the following interdependent properties that are used in the Configuration Project Dialog:

  • PDRConfigurationProjectClassChange: a calculated field that can change other calculated fields with $setValue.
  • Context.ArtifcatChangeItemsInitStatus: is changed by the PDRConfigurationProjectClassChange and uses $preventValueChanged() method. In the example, the property is used as a status with a numeric value. 

$setValue from PDRConfigurationProjectClassChange calculated field sets the value for another Context.ArtifcatChangeItemsInitStatus calculated field. If the value has changed, Context.ArtifcatChangeItemsInitStatus calculated field triggers call to all its dependencies, i.e. it will run the recalculation for PDRConfigurationProjectClassChange field value, which will lead to recursion as these two calculated fields will call one another.

To avoid recursion if the calculated field uses a dependency with $setValue it is recommended to set watchable to false, so that the Context.ArtifcatChangeItemsInitStatus calculated field will not be recalculated when the initStatus value changes.

In the given example, the $preventValueChanged() method is used as an alternative way to avoid recursion.

This method is called in Context.ArtifcatChnagedItemsInitStatus to prevent the call to all dependent calculated fields. So that this function is equivalent to setting watchable property to false, but for all dependencies at a time.

$format

Use format to adjust strings.

  • Declare variables in the Expression (JavaScript) section, for instance:
var a = "Hello";
var b = "World";
return $format("{0} {1}!", a, b); // "Hello World!"
  • Or format parameters, for instance, add necessary related data model properties and use them as parameters in the Expression (JavaScript) section, :

The application in the run-time will show the formatted description depending on the current enabled value as follows:

What You Can Call

The sandbox is thin — window is aliased to w and the whole page is reachable from an expression. Beyond the three helper functions above, expressions commonly rely on:

API Notes
mx.components.Utils.guidEmpty The all-zero GUID
JSON.parse / .stringify  
_. (lodash) _.find, _.get, _.forEach
moment() Dates; moment().utcOffset()
Utils.isGuid / .guid Validate / generate GUIDs
mx.Schema.getPickup(name) Returns a promise
$currentThemeColors .find(c => c.Key === 'error-text-color')?.Value

Modern syntax is supported: arrow functions, optional chaining (?.), nullish coalescing (??), template literals, and async.

Promises

Returning a promise is supported. The engine detects a thenable, awaits it, assigns the resolved value, then notifies listeners:

// Async lookup: return mx.Schema.getPickup("SPSAssetPickupType").then(function (data) { return _.find(data, { Value: type.$value }); }); 

The property holds its default value until the promise settles, so downstream logic must tolerate that initial value.

Ten Rules to Write By

In rough order of how much grief each one saves:

  1. Always return. On every code path. A missing return is easy to overlook and produces a silently broken property with no warning.
  2. Never omit .$value on a parameter. The silent bug is comparing an object to a scalar.
  3. Declare every property you read as a parameter. There is no other access path from an expression.
  4. Set Watchable to Disabled on parameters you read but must not react to. This breaks recalculation cycles before you hit the fifty-call ceiling.
  5. Return the declared type's shape — especially Currency and arrays.
  6. Check $sourceState.$value === 2 before touching source data. LOADED is 2, not 3.
  7. Keep it short. A long, tangled expression is hard to maintain and easy to break. Past a few dozen lines, the logic belongs in a Service Operation.
  8. No side effects on the page. Calling window., localStorage, setTimeout, .innerHTML, eval(, or alert( from an expression may work today, but expressions can run many times per interaction, in any order — side effects don't compose well with that.
  9. Strip console.log before shipping. Debug logging left in an expression clutters the console for every user.
  10. Match the language to the EditMode. JavaScript in a mode-3 property isn't an error; it's a broken column.

Debugging

Failures inside an expression are caught and logged, never thrown to the page. The property simply keeps its previous value and the widget carries on looking fine — which is exactly why it's worth knowing what to search for.

What to search for in the browser console:

Calculated property exception: { Path, Expression, Error }

Maximum relcalculations exeeded: Context.Foo.
  Last call triggered by: SubmitData.data.Bar

Warning!. Data model property `<path>` may contain child properties
  which could be ignored during data update

[data-model] $oldValue is a read-only snapshot of the previous state
  — .set() does not affect $value.

How to read them:

  • Exception: your expression threw. Check the error message and the console stack trace.
  • Maximum recalculations: you have a dependency cycle. The second line names the property that triggered the last call — that's usually your cycle.
  • Warning about child properties: you assigned a plain object over a property that has calculated children. The engine doesn't merge — it replaces.
  • $oldValue is read-only: you called .set() or .onChange() on $oldValue. It has no effect on $value.