> ## Documentation Index
> Fetch the complete documentation index at: https://developer.kodexa.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Events & Scripting

> Add dynamic behavior to Kodexa data forms with event handlers, the QuickJS runtime, the document API, and reactive triggers that respond to data changes.

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

| Field       | Type                  | Required | Description                                                              |
| ----------- | --------------------- | -------- | ------------------------------------------------------------------------ |
| `type`      | `string`              | Yes      | `"script"`, `"scriptRef"`, `"emit"`, `"store-action"`, or `"bus-event"`  |
| `target`    | `string`              | Yes      | Inline code, script name, event name, or action name depending on `type` |
| `params`    | `Record<string, any>` | No       | Parameters passed to the handler                                         |
| `condition` | `string`              | No       | JS expression; handler only fires when truthy                            |
| `debounce`  | `number`              | No       | Milliseconds 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

```json theme={null}
{
  "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

```json theme={null}
{
  "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`:

```json theme={null}
{
  "type": "script",
  "target": "kodexa.log.debug('clicked', ctx.$item?.uuid)"
}
```

**Named scripts** -- reusable functions in the form's `scripts` dictionary:

```json theme={null}
{
  "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:

```json theme={null}
{
  "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
    }
  }
}
```

| Field         | Type                     | Description                                      |
| ------------- | ------------------------ | ------------------------------------------------ |
| `source`      | `string`                 | The JavaScript function body                     |
| `description` | `string`                 | Human-readable purpose                           |
| `inputs`      | `Record<string, string>` | Expected input descriptions (documentation only) |
| `returns`     | `string`                 | Return type description                          |
| `debounce`    | `number`                 | Default 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 document wrappers, so scripts work the same way in both environments.

### Document Methods

| Method                       | Returns                | Description                         |
| ---------------------------- | ---------------------- | ----------------------------------- |
| `getAllDataObjects()`        | `DataObject[]`         | Every data object in the document   |
| `getDataObjectByUUID(uuid)`  | `DataObject` or `null` | Lookup by UUID                      |
| `getDataObjectsByPath(path)` | `DataObject[]`         | All objects matching the given path |
| `getMetadata()`              | `object`               | Document-level metadata             |

### Data Object Methods

| Method                      | Returns                                        |
| --------------------------- | ---------------------------------------------- |
| `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

| Method              | Returns                          |
| ------------------- | -------------------------------- |
| `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:

```javascript theme={null}
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

```javascript theme={null}
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.

| Field       | Type       | Description                                              |
| ----------- | ---------- | -------------------------------------------------------- |
| `script`    | `string`   | Named script reference from `scripts` or `scriptModules` |
| `triggerOn` | `string[]` | Attribute paths to watch (e.g., `["lineItem/amount"]`)   |
| `debounce`  | `number`   | Milliseconds to debounce (default `0`)                   |

```json theme={null}
{
  "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.

| Field      | Type     | Description                                                                                 |
| ---------- | -------- | ------------------------------------------------------------------------------------------- |
| `on`       | `string` | Event type: `"changed:dataAttribute"`, `"created:dataObject"`, `"validation:dataAttribute"` |
| `script`   | `string` | Named script reference                                                                      |
| `filter`   | `object` | Optional `{path?, dataObjectUUID?}` to narrow the scope                                     |
| `debounce` | `number` | Milliseconds to debounce (default `300`)                                                    |

```json theme={null}
{
  "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

<CardGroup cols={2}>
  <Card title="Bridge API" icon="plug" href="/guides/data-forms/bridge-api">
    Full kodexa.\* API reference with permissions and service bridges
  </Card>

  <Card title="Data Binding" icon="link" href="/guides/data-forms/data-binding">
    Context variables, expressions, and scoped data
  </Card>
</CardGroup>
