Skip to content
npm

AI: Logic and expression properties ​

This page defines which expression fields the agent may use and which rules it must follow.

Primary expression properties ​

PropertyWhen to useMust return
visibleIfShow an element (native field; return false to hide)true or false
disableIfMake an element inactivetrue or false
requireIfMake an element requiredtrue or false
readonlyIfMake an element read-onlytrue or false
resetIfClear an element under a conditiontrue or false
expressionCompute and write a valuestring, number, boolean, array, or object
validators[].applyIfEnable a validator conditionallytrue or false
validators[].conditionFailing check, error shown while truetrue or false
disallowAddRowsIfdynamicPanel / dynamicTable: disable the add buttontrue or false
disallowDeleteRowsIfdynamicPanel / dynamicTable: per row, remove that row's delete buttontrue or false
hideRowIfdynamicTable: per row, hide the rowtrue or false

Other expression usage points ​

The agent must know that expression logic does not live only in the main fields.

Data source parameters ​

The field is called params, never paramMap:

  • an element's dataSource.params[*].value, shaped [{ "name": ..., "value": ... }]
  • an action's params[*].value, the same shape
  • a table's own lazy-load request params, which are different: [{ "paramName": ..., "paramValue": ... }]
  • {placeholder} tokens inside a REST source's params.url and params.body

These can use expression fragments such as:

{personCode}
{row.id}
{__variables.route.mode}

Table columns ​

table.columnsConfig[*] can use:

  • visibleIf
  • controlActiveIf
  • controlEnabledIf

Inside a column of type: "element", the hosted element's own texts, templates, and event params resolve against the row it renders: {row.field}, a bare {field} for a sibling of the same row, {index}, and {value}. The same tokens work in a dynamicTable cell and a dynamicPanel row.

Event and action logic ​

Expressions are often used in actions in:

  • params[*].value
  • condition, which gates whether the action runs at all (per row for a table row action)
  • setValueValue when setValueMode is template or expression

When to use logicExecutionMode ​

If there is a dependency between fields, especially when expression is used, you typically need:

json
{
  "logicExecutionMode": "onChange"
}

This is especially important when:

  • one field fills another
  • one field changes another's required state
  • one field hides or disables another

Core writing rules ​

  • Use only NGX View Builder expression syntax, not full JavaScript.
  • Read field values via {fieldName} or {object.field}.
  • Use quotes for string values.
  • Do not add toNumber(...) by reflex. Number elements already hold numbers, so {price} * {quantity} works as written. Reach for it only when the value genuinely arrives as text: a text element, a data source field, a select whose values are strings, or a number element with valueStorageType: "string".
  • Use isEmpty(...) and notEmpty(...) for empty checks.
  • Use contains, containsAny, containsAll, and len for arrays.
  • For plain arithmetic across a few fields use sum(a, b, ...), min(a, b, ...) and max(a, b, ...). They take any number of values, skip empty ones and flatten arrays; sum of nothing is 0, min and max of nothing are null. Use sumArray and friends for a field inside each entry of a dynamic panel.
  • Round with roundNumber(value, digits), cut text with substring(text, start, end?) (0 based, end excluded), and get full years from a date with age({birthDate}). For an empty input roundNumber and age return null and substring returns "".

Correct examples ​

Auto-fill ​

json
{
  "name": "lastName",
  "type": "text",
  "expression": "{firstName} == 'John' ? 'Doe' : ''",
  "logicExecutionMode": "onChange"
}

Conditional required ​

json
{
  "name": "companyCode",
  "type": "text",
  "requireIf": "{personType} == 'company'",
  "logicExecutionMode": "onChange"
}

Show/hide ​

json
{
  "name": "vatCode",
  "type": "text",
  "visibleIf": "{country} == 'LT'"
}

Calculation ​

json
{
  "name": "totalAmount",
  "type": "number",
  "expression": "{price} * {quantity}",
  "logicExecutionMode": "onChange"
}
json
{
  "name": "totalWithVat",
  "type": "number",
  "expression": "roundNumber(sum({net}, {shipping}) * 1.21, 2)"
}

Bad examples ​

Self-reference expression ​

Incorrect:

json
{
  "name": "el2",
  "expression": "{el1} == 'John' ? 'Doe' : {el2}"
}

Why it is wrong:

  • the element references itself
  • this leads to unstable or non-functioning logic

Correct:

json
{
  "name": "el2",
  "expression": "{el1} == 'John' ? 'Doe' : ''",
  "logicExecutionMode": "onChange"
}

Wrong type for a boolean property ​

Incorrect:

json
{
  "visibleIf": "'yes'"
}

visibleIf must return true or false, not a string.

Reference to a non-existent field ​

Incorrect:

json
{
  "disableIf": "{customerType} == 'vip'"
}

if customerType does not exist in the form.

Expression property map by need ​

NeedProperty
Show/hide a fieldvisibleIf (return false to hide)
Show but prevent editingdisableIf or readonlyIf
Make required only in certain casesrequireIf
Reset value when condition changesresetIf
Automatically compute a valueexpression
Enable a validator conditionallyvalidators[].applyIf
Flag a custom validation errorvalidators[].condition (error while true)
Stop adding rows to a dynamic panel or table in some casesdisallowAddRowsIf (disallowAddRows: true locks it always)
Protect some rows from deletiondisallowDeleteRowsIf, e.g. {row.status} == "approved"

Working with arrays and element metadata ​

Conditions inside collection functions are written unquoted and evaluated once per entry. Entry fields are referenced bare, everything else in the view stays reachable through {...}:

text
countInArray({tasks}, status == "OPEN")
filterArray({users}, role == {__variables.requiredRole})
findInArray({products}, id == {selectedId})
existsInArray({items}, status == "ACTIVE")
sumArray({orderItems}, price)
joinInArray(mapArray(filterArray({tasks}, status == "OPEN"), title), "", ", ")

Also available: avgArray, getFirst(source, condition?), getLast(source, condition?). countInArray still accepts a plain field selector, so older structures keep working.

For element metadata rather than values:

CallReturns
getValue({select1})stored value, e.g. OPEN
getLabel({select1})displayed option label, e.g. Open issue
getLabel("select1", "CLOSED")label of a specific value
getElementProperty("el1", "label")any configured property, nested keys allowed
getProp({el1}, "placeholder")alias of getElementProperty
translate({row.status})value translated through localization.texts[<language>]
currentLanguage()active language code

In getLabel, getValue, and getProp the token names the element, it is not replaced by that element's value.

Writing values from an expression ​

CallDoes
setValue({variable1}, value)writes into a variable or an element data path
sumValue({variable1}, value)adds a number to the current value, returns the new total
pushValue({variable1}, value)appends to the target's array
flattenArray(source)flattens nested arrays into one level

setVar, sumVar, and pushVar are the same calls with the target always read as a variable name. addValue is an alias of sumValue.

Shorthand, where the leftmost token is the target and the right side reads normally:

text
{variable1} = {row.column3} + 40
{variable1} += {row.column3}

Rules for the agent:

  • The first argument names the target, do not replace it with a value.
  • setValue is safe in a recalculated expression, writing an unchanged value does nothing.
  • sumValue and pushValue add again on every run, so put them in an action, never in visibleIf, disableIf, or an expression property.
  • For a table inside a dynamic panel prefer one aggregate over accumulation: setValue({variable1}, sumArray({el1}, el5[].column3)).
  • A published column total is read as {el4.column1-total}, always in braces.

Contexts the agent may encounter ​

General form context ​

  • {fieldName}
  • {nested.field}
  • {__variables.*}

Dynamic blocks and tables ​

  • {row.field} inside a table cell, a dynamicTable row, a row action or a column template
  • {panel.field} inside a dynamicPanel entry
  • {item.field} for the candidate option in filterOptionsBy
  • {index} and {value} inside a cell
  • a published dynamicTable column total, {el1.column4-total}
  • in hideRowIf, disallowAddRowsIf and disallowDeleteRowsIf: {row.field} (also {panel.field} or {item.field}) for the row being judged, {index} for its position, {rowCount} for the number of rows, and any other {field} for the form. disallowAddRowsIf is judged once for the whole element, so it has {rowCount} and form fields but no row

A table's own live state ​

A table publishes its state so other elements can read it. __table.<tableName>.* is that table, bare __table.* is the last one touched:

rows allRows rowCount totalRecords page size sortField sortDirection quickSearch detailedFilters request selectedRows selectedKeys selectedItems selectedCount selectedRow selectedRowKey selectedRowIndex activeRow activeRowKey

text
{__table.el2.selectedCount} > 0
notEmpty({__table.el2.selectedRowKey})
{__table.el2.selectedRow}.column2

This is what drives selection-aware buttons, counters and master/detail screens. Full examples: Verified examples, 6.5 and 6.6.

In such cases, do not guess. If table or dynamicPanel is used, consult:

When to avoid expression ​

  • When the user only wants a static default text.
  • When defaultValue is sufficient.
  • When you only need to show or hide a section, not compute new data.

Final checklist ​

  1. Does the condition reference existing fields.
  2. Do boolean properties return a boolean value.
  3. Does expression not return the wrong type.
  4. Is there no self-reference.
  5. Is logicExecutionMode: "onChange" added where needed.