Logic & Behavior

Overview

The Code Editor in the multimodal platform empowers you to transform a static UI into a dynamic experience. Your Python code runs in the browser (via Pyodide), allowing you to respond to user actions, manipulate component values, and integrate with external APIs—without needing a separate backend for lightweight logic.


On the left-hand side of the editor, you’ll find:

  • A live preview of your form, where you can see how your interface behaves in real time.
  • A variables panel, allowing you to define reusable values accessible within your code.
  • A component inspector: Right-click any component in the preview to reveal its type and ID. You can copy the component’s ID directly from this menu for use in your code.

If your form uses visibility permissions, you can preview it from different perspectives by selecting a specific role and item status. The form will adjust dynamically to reflect the user experience for that configuration.

On the right-hand side, you’ll see the code editor. This is where you write custom Python logic, interact with components, use predefined variables, and build functionality specific to your annotation workflows.

Using Pyodide

The Code Editor leverages Pyodide, a Python runtime compiled to WebAssembly (Wasm). This enables Python scripts to run client-side, in the user’s browser. While it’s incredibly flexible, keep in mind:

  • Limited Library Support
    You can’t install every Python package by default, though many popular libraries are available or can be installed dynamically with pyodide_js.
  • Performance Constraints
    Pyodide handles small to medium tasks well but may struggle with heavy data processing or other compute intensive tasks. For larger workflows, see the Orchestrate Documentation.

Installing packages

To load external Python packages in the code editor, use the pyodide_js module:

import pyodide_js 

async def load_libraries(): 
await pyodide_js.loadPackage('some_package') 
import some_package 
# Now you can use some_package

Note: Packages are installed during the first run of each session. If you switch between items within the editor during the same session, the packages remain in place and are not redownloaded. However, closing and reopening the editor starts a new session, which will trigger a fresh installation.

Base Code Structure

When you create or update a form in the UI Builder, the platform automatically generates a base Python file named __init__.py. This file includes:

  1. Component Path Definitions
    • Mappings for each component’s unique ID or group path.
  2. Event Handler Stubs
    • Predefined functions (on_<component_id>_<event_type>) for standard component events.

You can freely extend __init__.py with custom logic, variables, or imports. If you need a more modular approach, you can add additional Python files and import them into __init__.py.

File Management

Files are a way for you to manage and sort your form’s code without maintaining it all in one compacted place. You can categorize different areas of your code into separate files, then simply import the desired data within to the file that requires it.

To create a new file:

  1. Click the “+” icon at the top of the Code Editor.
  2. Enter a filename in the dialog (e.g., helpers.py).
  3. Click Create.

To import a specific variable, function, or class from one file to another, include this import sequence in the destination file:

from helpers import calculate_sum

Run

To test your code after editing, you must click Run to update the form preview.

Regenerate

After your form's base code is generated, when you make changes to your form in the UI builder, the code editor will be modified accordingly. Clicking Regenerate will replace the code with an initial state based on the updated form UI. If there are any lines of custom code that you want to keep, make sure to have a copy of them with you before regenerating the code editor.

Event Handlers

Event handlers are special functions that allow you to attach custom behavior to user interactions and system events within the Code Editor. These functions follow a strict naming convention:

def on_<component_id>_<event_type>(path, ...):
	# Your code here

Where:

  • component_id is the unique ID of the UI element.
  • event_type is one of the supported interaction or lifecycle events listed below.

Supported Component Events

Value Change (change)

Triggered when the value of a component changes (e.g., typing in a text field).

Signature:

def on_<component_id>_change(path, value):
    # Your custom code
    return
  • path: The component’s path (['component_id'] or ['group_id', row_index, 'component_id']).
  • value: The new value of the component.

Button Click (click):

Triggered when a button is clicked.

Signature:

def on_<component_id>_click(path):
    # Custom logic
    return
  • path: Path to the clicked button

Web Component Message (message):

Triggered when a WebComponent sends a message using postMessage.

Signature:

def on_<component_id>_message(path, value):
    # Your custom code
    return
  • path: Path to the WebComponent
  • value: Message sent from the component

Group Row Deleted (deleted):

Triggered when a row is removed from a Group component (e.g., clicking the ❌ icon).

Signature:

def on_<group_id>_deleted(path):
    # Your custom code
    return
  • path: Path to the deleted row

Group Selected

Triggered when a row is selected.

def on_<group_id>selected(path):
    # Your custom code
    return
  • path: Path to the selected row

Lifecycle Hooks

In addition to direct component event handlers, the editor provides lifecycle hooks for major item actions:

  • post_hook() - Called when an item is closed in the editor.
  • before_save_hook() - Called immediately before saving. Useful for final validations or data transformations.
  • on_saved_hook() - Called right after an item is saved.
  • before_status_change_hook() - Called before an item’s status changes (e.g., from “InProgress” to “QualityCheck”).
  • on_status_changed_hook() - Called after the item’s status has changed.

Use these hooks to enforce consistency checks, log events, or trigger custom actions when items are saved or statuses change.

Interacting with Components

Component Paths

Every component is identified by a path, which depends on whether it’s standalone or nested in Groups:

  • Standalone: ['component_id']
  • Grouped: ['group_id', <row_index>, 'component_id']
  • Nested Groups: ['group_1', <row_index_1>, 'group_2', <row_index_2>, 'component_id']

📘

Note

use ‘-1’ as row_index to get the last row.

Built-in Functions

In the code editor, you can access a set of built-in helper functions for interacting with components, sending messages, and manipulating UI state. These are available via the sa module:

import sa

All functions are accessed through the sa. namespace:

Component Interaction

  • sa.changeTab(path: list[tuple[str, int]], index: int)
    Switches the selected tab in a Tab component based on the provided path and tab index.

  • sa.deleteRow(path: list[tuple[str, int]])
    Deletes a row in a Group component at the specified path. The path must include the row index.

  • sa.enable(path: list[tuple[str, int]])
    Enables a component (e.g., input, button) located at the given path.

  • sa.disable(path: list[tuple[str, int]])
    Disables a component at the given path.

  • sa.getGroupLength(path: list[tuple[str, int]]) -> int
    Returns the number of repeated rows in a Group component.

  • sa.repeatRow(path: list[tuple[str, int]], index: int) -> list[tuple[str, int]]
    Appends a new row to a group at the specified path and returns the path of the newly created row.

Value & State Access

  • sa.getValue(path: list[tuple[str, int]]) -> Any
    Returns the current value of a component at the given path.

  • sa.setValue(path: list[tuple[str, int]], value: Any)
    Sets the value of the component. The value type must match the component's expected format: str, int, float, list[str], tuple[int, float], etc.

  • sa.setLoading(state: bool, path: list[tuple[str, int]])
    Sets the loading state of a Button component.

Payloads & Web Communication

  • sa.getPayload(name: str) -> Any
    Retrieves the contents of a JSON file attached to the current item by name.

  • sa.postMessageToWebComponent(path: list[tuple[str, int]], data: Any)
    Sends data to a custom WebComponent.

User Messaging

Displays a message on the bottom-left of the interface. Useful for user feedback or error handling.

  • sa.showError(message: str) – Displays an error message.
  • sa.showInfo(message: str) – Displays an informational message.
  • sa.showSuccess(message: str) – Displays a success message.
  • sa.showWarning(message: str) – Displays a warning message.

Best practices

  1. Backup Custom Code
    • Always save or copy your code elsewhere before regenerating the base code. The regeneration process can overwrite changes in init.py.
  2. Organize Files
    • Keep business logic, helper functions, and main event handlers in separate files for clarity and maintainability.
  3. Test Frequently
    • Use the Run button in the Code Editor to verify changes in real-time. Log or print outputs to the console to debug.
  4. Avoid Long-Running Tasks
    • Pyodide runs in the main UI thread. Heavy operations can freeze the interface. For large computations, consider using Orchestrate.
  5. Leverage Lifecycle Hooks
    • Validate data, run housekeeping tasks, or log user actions at key points in the annotation process (before/after save, on status change, etc.).

Next steps

  • Learn About Custom Components: Explore how to build or embed custom HTML/JS interfaces.
  • Discover Orchestrate: For tasks that exceed Pyodide’s constraints, see our orchestration system for containerized Python scripts.
  • Review Advanced Examples: Check out the Code Editor Examples for a guided walkthrough on event handlers, group manipulation, and more.