Event Handling

Responding to user interactions in Orbis

Event Handling

Events connect user interactions to actions. When a user clicks a button, submits a form, or changes an input, events trigger action sequences that update state and interact with the system.

Overview

Event handling in Orbis follows this pattern:

graph LR
    User[User Interaction ] --> Event[Event Triggered ]
    Event --> Actions[Action Sequence ]
    Actions --> State[State Updates ]
    State --> UI[UI Re-render ]

Event Handler Structure

Events are defined in the events property of components:

json
{
  "type": "Button",
  "label": "Click Me",
  "events": {
    "on_click": [
      { "type": "update_state", "path": "clicked", "value": true },
      { "type": "show_toast", "message": "Button clicked!" }
    ]
  }
}

Each event handler is an array of actions that execute in sequence.

Available Events

Mouse Events

EventTriggered WhenComponents
on_clickElement is clickedMost components
on_double_clickElement is double-clickedContainer, Card, etc.
on_mouse_enterMouse enters elementContainer, Card
on_mouse_leaveMouse leaves elementContainer, Card

Form Events

EventTriggered WhenComponents
on_changeInput value changesField, Form inputs
on_submitForm is submittedForm
on_focusInput gains focusField
on_blurInput loses focusField

Keyboard Events

EventTriggered WhenComponents
on_key_downKey is pressedInput fields
on_key_upKey is releasedInput fields

Data Events

EventTriggered WhenComponents
on_row_clickTable row is clickedTable, List
on_row_double_clickTable row is double-clickedTable
on_selectItem is selectedTable, Select
on_clearSelection is clearedSelect
on_searchSearch query changesSearchable components
on_page_changeTable page changesTable
on_sort_changeTable sort changesTable
on_filter_changeFilter is appliedTable

Lifecycle Events

EventTriggered WhenComponents
on_loadComponent/data loadsImage, async components
on_errorError occursImage, async components
on_openOverlay opensModal, Dropdown
on_closeOverlay closesModal, Dropdown, Alert

Event Object

Event handlers receive context about the event through special variables.

$event

The raw event object:

json
{
  "events": {
    "on_click": [
      {
        "type": "update_state",
        "path": "lastClick",
        "value": "$event"
      }
    ]
  }
}

$event.value

For input events, the current value:

json
{
  "type": "Field",
  "name": "search",
  "events": {
    "on_change": [
      {
        "type": "update_state",
        "path": "searchQuery",
        "value": "$event.value"
      }
    ]
  }
}

$event.target

The element that triggered the event:

json
{
  "events": {
    "on_focus": [
      {
        "type": "update_state",
        "path": "focusedField",
        "value": "$event.target.name"
      }
    ]
  }
}

Row/Item Context

When events occur within lists or tables, additional context is available:

$row

Current table row data:

json
{
  "type": "Table",
  "events": {
    "on_row_click": [
      {
        "type": "update_state",
        "path": "selectedUser",
        "value": "$row"
      },
      {
        "type": "navigate",
        "to": "/users/{{$row.id}}"
      }
    ]
  }
}

$item

Current loop/list item:

json
{
  "type": "Loop",
  "dataSource": "state:items",
  "template": {
    "type": "Button",
    "label": "Delete {{$item.name}}",
    "events": {
      "on_click": [
        {
          "type": "update_state",
          "path": "items",
          "mode": "remove",
          "value": "{{$item.id}}"
        }
      ]
    }
  }
}

$index

Current iteration index:

json
{
  "type": "Loop",
  "template": {
    "type": "Button",
    "label": "Item {{$index + 1}}",
    "events": {
      "on_click": [
        {
          "type": "update_state",
          "path": "selectedIndex",
          "value": "$index"
        }
      ]
    }
  }
}

Action Sequences

Events execute actions in sequence. Actions can be:

Independent Actions

Run in order, each completing before the next:

json
{
  "on_click": [
    { "type": "update_state", "path": "step", "value": 1 },
    { "type": "update_state", "path": "step", "value": 2 },
    { "type": "show_toast", "message": "Step is now 2" }
  ]
}

Conditional Flow

Branch based on conditions:

json
{
  "on_click": [
    {
      "type": "conditional",
      "condition": "{{state.isValid}}",
      "then": [
        { "type": "call_api", "api": "submit" }
      ],
      "else": [
        { "type": "show_toast", "message": "Please fix errors", "level": "error" }
      ]
    }
  ]
}

Async Actions

API calls with callbacks:

json
{
  "on_click": [
    { "type": "set_loading", "loading": true },
    {
      "type": "call_api",
      "api": "my-plugin.saveData",
      "on_success": [
        { "type": "show_toast", "message": "Saved!", "level": "success" }
      ],
      "on_error": [
        { "type": "show_toast", "message": "Failed: {{$error.message}}", "level": "error" }
      ]
    },
    { "type": "set_loading", "loading": false }
  ]
}

Warning

The set_loading after call_api runs immediately, not after the API completes. Use the on_success/on_error callbacks for post-API actions.

Component-Specific Events

Button

json
{
  "type": "Button",
  "label": "Submit",
  "events": {
    "on_click": [{ "type": "call_api", "api": "submit" }]
  }
}

Field (Input)

json
{
  "type": "Field",
  "name": "email",
  "fieldType": "email",
  "events": {
    "on_change": [{ "type": "update_state", "path": "email", "value": "$event.value" }],
    "on_focus": [{ "type": "update_state", "path": "focused", "value": "email" }],
    "on_blur": [{ "type": "validate_form", "formId": "my-form" }]
  }
}

Form

json
{
  "type": "Form",
  "id": "contact-form",
  "events": {
    "on_submit": [
      { "type": "validate_form", "formId": "contact-form" },
      {
        "type": "conditional",
        "condition": "{{state.formValid}}",
        "then": [
          { "type": "call_api", "api": "submitContact" }
        ]
      }
    ]
  }
}

Table

json
{
  "type": "Table",
  "columns": [...],
  "dataSource": "state:users",
  "events": {
    "on_row_click": [
      { "type": "update_state", "path": "selectedUser", "value": "$row" }
    ],
    "on_select": [
      { "type": "update_state", "path": "selectedUsers", "value": "$event.value" }
    ],
    "on_page_change": [
      { "type": "update_state", "path": "currentPage", "value": "$event.value" }
    ],
    "on_sort_change": [
      {
        "type": "update_state",
        "path": "sort",
        "value": {
          "column": "$event.column",
          "direction": "$event.direction"
        }
      }
    ]
  }
}
json
{
  "type": "Modal",
  "id": "confirm-dialog",
  "events": {
    "on_open": [
      { "type": "update_state", "path": "modalOpen", "value": true }
    ],
    "on_close": [
      { "type": "update_state", "path": "modalOpen", "value": false },
      { "type": "update_state", "path": "selectedItem", "value": null }
    ]
  }
}

Event Bubbling

Events don’t bubble by default. Each component handles its own events.

To handle parent clicks while ignoring child clicks:

json
{
  "type": "Container",
  "events": {
    "on_click": [{ "type": "update_state", "path": "container_clicked", "value": true }]
  },
  "children": [
    {
      "type": "Button",
      "label": "Inner Button",
      "events": {
        "on_click": [
          // This runs, container's on_click does NOT run
          { "type": "update_state", "path": "button_clicked", "value": true }
        ]
      }
    }
  ]
}

Best Practices

Keep Handlers Focused

json
// Good - clear purpose
{
  "on_click": [
    { "type": "update_state", "path": "isOpen", "value": true }
  ]
}

// ❌ Avoid - too many concerns
{
  "on_click": [
    { "type": "update_state", "path": "isOpen", "value": true },
    { "type": "update_state", "path": "lastOpened", "value": "now" },
    { "type": "call_api", "api": "logEvent" },
    { "type": "show_toast", "message": "Opened" }
  ]
}

Use Descriptive State Paths

json
// Good
{ "type": "update_state", "path": "form.isSubmitting", "value": true }

// ❌ Avoid
{ "type": "update_state", "path": "s", "value": true }

Handle Loading and Errors

Always handle async operation states:

json
{
  "on_click": [
    { "type": "set_loading", "target": "submit", "loading": true },
    {
      "type": "call_api",
      "api": "submit",
      "on_success": [
        { "type": "set_loading", "target": "submit", "loading": false },
        { "type": "show_toast", "message": "Success!" }
      ],
      "on_error": [
        { "type": "set_loading", "target": "submit", "loading": false },
        { "type": "show_toast", "message": "Failed!", "level": "error" }
      ]
    }
  ]
}

Debugging Events

Log State Changes

Use temporary state to debug:

json
{
  "on_click": [
    { "type": "update_state", "path": "debug.lastEvent", "value": "button clicked" },
    { "type": "update_state", "path": "debug.timestamp", "value": "{{Date.now()}}" }
  ]
}

Development Logging

Enable debug mode:

bash
RUST_LOG=debug,orbis=trace bun run tauri dev

Next Steps