Skip to main content
V2 data forms combine a declarative event system with a sandboxed JavaScript runtime to add dynamic behavior to forms. Events are attached to components, scripts run in QuickJS WebAssembly, and triggers react to changes in the underlying document. This page covers all four pieces and how they fit together.

Event System

Events are declared on any UINode via the events property. Each key is a component event name, and the value is a single EventConfig or an array of them.

EventConfig Fields

FieldTypeRequiredDescription
typestringYes"script", "scriptRef", "emit", "store-action", or "bus-event"
targetstringYesInline code, script name, event name, or action name depending on type
paramsRecord<string, any>NoParameters passed to the handler
conditionstringNoJS expression; handler only fires when truthy
debouncenumberNoMilliseconds to wait before executing (resets on each trigger)

Handler Types

  • script — evaluates target as an inline JavaScript expression with access to ctx and the kodexa.* bridge.
  • scriptRef — calls a named function from the form’s scripts or scriptModules registry.
  • emit — re-emits the event from the schema root so parent components can listen.
  • store-action — dispatches a Pinia store action (reserved for future use).
  • bus-event — publishes on the application event bus (reserved for future use).

Example: Click and Change Handlers

{
  "component": "card:dataAttributeEditor",
  "props": { "taxon": "invoice/totalAmount" },
  "events": {
    "click": {
      "type": "script",
      "target": "kodexa.navigation.focusAttribute(ctx.$item.uuid, 'invoice/totalAmount')"
    },
    "change": {
      "type": "scriptRef",
      "target": "recalculateTotal",
      "condition": "ctx.newValue !== ctx.oldValue",
      "debounce": 200
    }
  }
}
Multiple handlers can be attached to the same event by passing an array of EventConfig objects. Each handler’s condition is evaluated independently.

QuickJS Runtime

All scripts run in a QuickJS WebAssembly sandbox — isolated from the browser, memory-safe, and protected by a configurable execution timeout.

Timeout Configuration

{
  "bridge": {
    "maxExecutionMs": 2000
  }
}
The default limit is 1000ms. If a script exceeds it, the runtime throws a "Script error: interrupted" error.

Defining Scripts

Inline — short expressions placed directly in events[].target:
{
  "type": "script",
  "target": "kodexa.log.debug('clicked', ctx.$item?.uuid)"
}
Named scripts — reusable functions in the form’s scripts dictionary:
{
  "version": "2",
  "scripts": {
    "formatCurrency": "function(ctx) { const v = ctx.value; return v != null ? '$' + Number(v).toFixed(2) : '--'; }",
    "recalculateTotal": "function(ctx) { return ctx.items.reduce((s, i) => s + (i.amount || 0), 0); }"
  },
  "nodes": []
}
Named scripts are invoked through scriptRef event handlers or computed bindings on a UINode.

Script Modules

For scripts that need documentation and input/output metadata, use scriptModules. A ScriptModule wraps a function with additional fields:
{
  "scriptModules": {
    "computeSubtotal": {
      "source": "function(ctx) { return ctx.items.reduce((sum, i) => sum + (i.amount || 0), 0); }",
      "description": "Sums amount attributes across child line items",
      "inputs": { "items": "DataObject[] with amount attribute" },
      "returns": "number",
      "debounce": 200
    }
  }
}
FieldTypeDescription
sourcestringThe JavaScript function body
descriptionstringHuman-readable purpose
inputsRecord<string, string>Expected input descriptions (documentation only)
returnsstringReturn type description
debouncenumberDefault debounce in milliseconds

Document API

Scripts can access the current document through the loadDocument() global, which returns a read-only document proxy. This API mirrors the server-side Goja document wrappers, so scripts work the same way in both environments.

Document Methods

MethodReturnsDescription
getAllDataObjects()DataObject[]Every data object in the document
getDataObjectByUUID(uuid)DataObject or nullLookup by UUID
getDataObjectsByPath(path)DataObject[]All objects matching the given path
getMetadata()objectDocument-level metadata

Data Object Methods

MethodReturns
getPath()Object path (e.g., "invoice/lineItem")
getIDString()String ID
getUUID()UUID string
getName()Display name
getAttributes()All attributes as an array
getAttributeByName(name)First attribute matching by tag or path suffix
getAttributesByName(name)All attributes matching by tag or path suffix
getAttribute(label)Attribute matching by exact tag or path
getChildren()Child data objects
getChildrenByPath(path)Children filtered by path
getParent()Parent data object or null
hasTaxonomy()Whether the object has a taxonomy reference

Attribute Methods

MethodReturns
getValue()Typed value (see priority below)
getStringValue()String representation
getTag()Taxonomy tag
getPath()Full attribute path
getConfidence()Extraction confidence score
getDataFeatures()Feature map
getValue() returns the first non-null value in this priority order: decimal, boolean, date, dateTime, integer, string.

Write Operations

To mutate the document, obtain a writable proxy through the bridge:
var doc = kodexa.document.writableSnapshot();
Writable proxies add these methods:
  • Document: createDataObject({path, taxonomyRef?}), deleteDataObject(uuid)
  • Data object: addAttribute({tag, path, value?}), addChild({path})
  • Attribute: setValue(value), setStringValue(value)
Write operations require both document:read and data:write bridge permissions.

Example: Sum Line Item Amounts

function(ctx) {
  var doc = loadDocument();
  var lineItems = doc.getDataObjectsByPath("invoice/lineItem");
  var total = 0;
  for (var i = 0; i < lineItems.length; i++) {
    var amt = lineItems[i].getAttributeByName("amount");
    if (amt) {
      total += Number(amt.getValue()) || 0;
    }
  }
  return total;
}

Script Triggers

Script triggers react to changes in data attribute values. They are declared as a scriptTriggers array on the form definition and invoke a named script whenever one of the watched attribute paths changes.
FieldTypeDescription
scriptstringNamed script reference from scripts or scriptModules
triggerOnstring[]Attribute paths to watch (e.g., ["lineItem/amount"])
debouncenumberMilliseconds to debounce (default 0)
{
  "version": "2",
  "scripts": {
    "recalcTotal": "function(ctx) { var doc = loadDocument(); var items = doc.getDataObjectsByPath('invoice/lineItem'); var sum = 0; for (var i = 0; i < items.length; i++) { var a = items[i].getAttributeByName('amount'); if (a) sum += Number(a.getValue()) || 0; } return sum; }"
  },
  "scriptTriggers": [
    {
      "script": "recalcTotal",
      "triggerOn": ["lineItem/amount", "lineItem/quantity"],
      "debounce": 300
    }
  ]
}
When any attribute matching lineItem/amount or lineItem/quantity changes, the recalcTotal script runs after a 300ms debounce window.

Event Triggers

Event triggers react to WASM document events such as attribute changes, object creation, and validation results. They are declared as an eventTriggers array on the form.
FieldTypeDescription
onstringEvent type: "changed:dataAttribute", "created:dataObject", "validation:dataAttribute"
scriptstringNamed script reference
filterobjectOptional {path?, dataObjectUUID?} to narrow the scope
debouncenumberMilliseconds to debounce (default 300)
{
  "version": "2",
  "scripts": {
    "validateAmount": "function(ctx) { var doc = loadDocument(); var obj = doc.getDataObjectByUUID(ctx.dataObjectUUID); if (!obj) return; var amt = obj.getAttributeByName('amount'); if (amt && Number(amt.getValue()) < 0) { kodexa.log.warn('Negative amount detected', ctx.dataObjectUUID); } }"
  },
  "eventTriggers": [
    {
      "on": "changed:dataAttribute",
      "script": "validateAmount",
      "filter": { "path": "lineItem/amount" },
      "debounce": 500
    }
  ]
}
Additional event types emitted by the WASM layer include deleted:dataObject, deleted:dataAttribute, recalculated:dataAttribute, and validationCleared:dataAttribute. Use these with event triggers to keep form state synchronized with document changes.

Next Steps

Bridge API

Full kodexa.* API reference with permissions and service bridges

Data Binding

Context variables, expressions, and scoped data