Skip to main content
V2 data forms provide a declarative event system that connects component interactions to actions. Instead of writing Vue event listeners in code, you declare handlers in the events object on any UINode. The renderer wires up the handlers at render time and routes events through the appropriate action type. This guide covers all five event types, condition expressions, debounce, attaching multiple handlers to a single event, and practical examples for each type. For the bridge methods available to script handlers, see the Bridge API reference. The event system is designed to keep form definitions self-contained. All behavior is declared in JSON alongside the component tree, making forms portable and auditable without reading source code.

Event Configuration

Events are declared in the events object on a UINode. Each key is an event name (matching what the component emits), and the value is either a single EventConfig or an array of EventConfig objects.
{
  "component": "card:dataAttributeEditor",
  "props": { "taxon": "invoice/totalAmount" },
  "events": {
    "change": {
      "type": "script",
      "target": "kodexa.log.debug('Value changed')"
    }
  }
}

EventConfig Fields

FieldTypeRequiredDescription
typestringYesOne of: "script", "scriptRef", "emit", "store-action", "bus-event"
targetstringYesDepends on type — see each type below
paramsRecord<string, any>NoAdditional parameters passed to the handler
conditionstringNoJavaScript expression that must evaluate to truthy for the handler to run
debouncenumberNoMilliseconds to wait before executing (resets on each trigger)

Event Types

script

Evaluates an inline JavaScript expression. This is the most common handler type for simple operations.
FieldValue
type"script"
targetA JavaScript expression to evaluate
The expression runs in the same context as binding expressions, with access to ctx, $item, $index, and the kodexa.* bridge.
{
  "events": {
    "click": {
      "type": "script",
      "target": "kodexa.data.setAttribute(ctx.$item.uuid, 'invoice/status', 'reviewed')"
    }
  }
}
Common uses: Logging, setting attributes, toggling form state, navigating to attributes.

scriptRef

Calls a named script from the scripts or scriptModules registry. The script is invoked with the current data context.
FieldValue
type"scriptRef"
targetThe name of a registered script
paramsOptional parameters merged into the script context
{
  "version": "2",
  "scripts": {
    "validateAndHighlight": "function(ctx) { if (!ctx.value) { kodexa.log.warn('Missing value for', ctx.path); } }"
  },
  "nodes": [
    {
      "component": "card:dataAttributeEditor",
      "props": { "taxon": "invoice/totalAmount" },
      "events": {
        "blur": {
          "type": "scriptRef",
          "target": "validateAndHighlight",
          "params": { "path": "invoice/totalAmount" }
        }
      }
    }
  ]
}
Common uses: Complex validation, multi-step operations, reusable logic shared across events.

emit

Re-emits the event from the schema root component. This allows parent components or the host application to listen for events from within the form.
FieldValue
type"emit"
targetThe event name to emit
{
  "events": {
    "click": {
      "type": "emit",
      "target": "row-selected"
    }
  }
}
The emitted event can carry a payload. The original event data from the component is passed through as the payload. Common uses: Communicating selections, actions, or state changes to the host application.

store-action

Dispatches an action on the workspace or application store. This type is reserved for future use and currently acts as a no-op.
FieldValue
type"store-action"
targetThe store action name
paramsParameters for the store action
{
  "events": {
    "submit": {
      "type": "store-action",
      "target": "saveDocument",
      "params": { "validate": true }
    }
  }
}
Store actions are not yet implemented in the current release. This event type is included for forward compatibility. Use script with kodexa.data bridge methods for data mutations in the meantime.

bus-event

Publishes an event on the application event bus. This type is reserved for future use and currently acts as a no-op.
FieldValue
type"bus-event"
targetThe bus event name
paramsParameters to include in the event payload
{
  "events": {
    "change": {
      "type": "bus-event",
      "target": "data-form:attribute-changed",
      "params": { "source": "invoiceForm" }
    }
  }
}
Bus events are not yet implemented in the current release. This event type is included for forward compatibility.

Condition Expressions

Every EventConfig can include a condition expression. The handler only executes when the condition evaluates to a truthy value. The condition has access to the same context as binding expressions.
{
  "events": {
    "click": {
      "type": "script",
      "target": "kodexa.data.setAttribute(ctx.$item.uuid, 'invoice/status', 'approved')",
      "condition": "ctx.$item?.attributes?.find(a => a.path === 'invoice/status')?.value === 'pending'"
    }
  }
}
In this example, the click handler only fires when the invoice status is "pending". Clicks on invoices with other statuses are ignored.

Condition Use Cases

PatternCondition Expression
Only when data exists"ctx.dataObjects?.length > 0"
Only for specific types"ctx.$item?.path === 'invoice'"
Only when not read-only"!ctx.$item?.locked"
Only for non-empty values"ctx.newValue != null && ctx.newValue !== ''"

Debounce

The debounce field delays handler execution. Each new trigger resets the timer. This is useful for handlers attached to high-frequency events like input or change on text fields.
{
  "events": {
    "input": {
      "type": "script",
      "target": "kodexa.data.setAttribute(ctx.$item.uuid, 'invoice/search', ctx.query)",
      "debounce": 300
    }
  }
}
With a 300ms debounce, the handler waits 300ms after the last keystroke before executing. If the user types another character within that window, the timer resets.
Script modules (scriptModules) can also declare a default debounce value. When both the module and the event config specify a debounce, the event config value takes precedence.

Multiple Handlers on the Same Event

Pass an array of EventConfig objects to attach multiple handlers to a single event. Handlers execute in order, and each one’s condition is evaluated independently.
{
  "events": {
    "change": [
      {
        "type": "script",
        "target": "kodexa.log.debug('Value changed to:', ctx.newValue)"
      },
      {
        "type": "scriptRef",
        "target": "recalculateTotal"
      },
      {
        "type": "emit",
        "target": "attribute-updated",
        "condition": "ctx.newValue !== ctx.oldValue"
      }
    ]
  }
}
In this example, when the change event fires:
  1. The first handler always logs the new value.
  2. The second handler calls the recalculateTotal script.
  3. The third handler emits attribute-updated, but only if the value actually changed.

Examples

Click to Navigate

Navigate to the source document location when a cell is clicked:
{
  "component": "card:label",
  "bindings": {
    "label": "ctx.$item?.attributes?.find(a => a.path === 'invoice/invoiceNumber')?.value ?? '--'"
  },
  "events": {
    "click": {
      "type": "script",
      "target": "kodexa.navigation.focusAttribute(ctx.$item.uuid, 'invoice/invoiceNumber')"
    }
  }
}

Conditional Status Update

Only allow status change when the current status is ‘draft’:
{
  "events": {
    "click": {
      "type": "script",
      "target": "kodexa.data.setAttribute(ctx.$item.uuid, 'invoice/status', 'submitted')",
      "condition": "kodexa.data.getAttribute(ctx.$item.uuid, 'invoice/status') === 'draft'"
    }
  }
}
Filter results as the user types, with debouncing:
{
  "events": {
    "input": {
      "type": "script",
      "target": "kodexa.form.set('searchQuery', ctx.value)",
      "debounce": 250
    }
  }
}

Log and Emit

Log for debugging and emit for the host application:
{
  "events": {
    "select": [
      {
        "type": "script",
        "target": "kodexa.log.debug('Selected:', ctx.$item?.uuid)"
      },
      {
        "type": "emit",
        "target": "item-selected"
      }
    ]
  }
}

Validation on Blur

Run validation when a field loses focus:
{
  "version": "2",
  "scripts": {
    "validateRequired": "function(ctx) { if (!ctx.value || ctx.value.trim() === '') { kodexa.log.warn('Required field is empty:', ctx.fieldPath); } }"
  },
  "nodes": [
    {
      "component": "card:dataAttributeEditor",
      "props": { "taxon": "invoice/vendorName" },
      "events": {
        "blur": {
          "type": "scriptRef",
          "target": "validateRequired",
          "params": { "fieldPath": "invoice/vendorName" }
        }
      }
    }
  ]
}

Next Steps