Portal Community

Basic Usage

// Set a computed total from DataGrid data
function onDataChange(widget, variables, actions, context) {
  const rows = widget.data?.rows ?? [];
  const total = rows.reduce((sum, r) => sum + (r.dealValue ?? 0), 0);

  variables.set('totalDealValue', total);
  // → All widgets using {{ variables.totalDealValue }} re-render with the new value
}

Variables Must Be Declared First

Before setting a variable from Custom JS, it must be declared in the App Variables panel (App Settings → Variables). Setting an undeclared variable is a no-op — it will not create a new variable at runtime:

// App Variables declaration (in App Studio App Settings → Variables):
{
  "name": "totalDealValue",
  "type": "number",
  "defaultValue": 0
}

// Then in Custom JS:
variables.set('totalDealValue', 42500);  // Works — variable is declared

variables.set('undeclaredVar', 99);      // Ignored — not declared

Supported Value Types

TypeExampleNotes
numbervariables.set('count', 42)Integers and floats
stringvariables.set('label', 'Active')Any string value
booleanvariables.set('isLoaded', true)Used for visibility toggle patterns
objectvariables.set('summary', { count: 5, total: 1200 })Object variables; use dot notation in tokens: {{ variables.summary.total }}
arrayvariables.set('selectedIds', ['id-1', 'id-2'])Useful for multi-select state
nullvariables.set('activeRecord', null)Clears the variable to its null state

Reading Before Writing

// Read the current filter state, toggle it, write it back
function onUserAction(widget, variables, actions, context) {
  const currentFilter = variables.get('showOnlyActive');  // boolean

  variables.set('showOnlyActive', !currentFilter);        // toggle
}

Batch Setting Multiple Variables

Set multiple variables in sequence within a single script execution. All changes are applied as a batch — the reactive system processes them together, avoiding multiple intermediate re-renders:

function onDataChange(widget, variables, actions, context) {
  const rows = widget.data?.rows ?? [];

  const activeRows = rows.filter(r => r.status === 'active');
  const closedRows = rows.filter(r => r.status === 'closed');

  variables.set('activeCount', activeRows.length);
  variables.set('closedCount', closedRows.length);
  variables.set('totalValue', rows.reduce((s, r) => s + (r.value ?? 0), 0));
  variables.set('avgValue', rows.length > 0 ? total / rows.length : 0);
  // All four updates processed together — single re-render pass
}
Avoid infinite loops If Widget A sets a variable on onDataChange, and that variable is a data source param for Widget A itself, setting it will trigger Widget A to reload, which fires onDataChange again. Guard against this with a previous-value check or by using a separate derived variable that Widget A does not bind its own data source to.