Skip to content

Events and Interactions

htag provides a seamless way to handle user interactions on the server side.

Event Handlers

You can attach event handlers to any Tag component using dictionary-style access (e.g., ["onclick"]). While underscored keyword arguments like _onclick are still valid in constructors, the dictionary syntax is the standard for direct attribute management.

from typing import Any from htag import Tag

def my_callback(e: Any) -> None: print(f"Clicked on {e.target.id}") e.target.add(Tag.span("!"))

Attached via dictionary syntax

btn["onclick"] = my_callback

OR in constructor

btn = Tag.button("Click me", _onclick=my_callback)

### The Event Object

The `e` argument passed to the callback is an `Event` object containing:

- `e.target`: The `Tag` instance that triggered the event.
- `e.name`: The name of the event (e.g., "click").
- Data attributes like `e.value` (for inputs), `e.checked` (for checkboxes/radios), `e.x`, `e.y` (for mouse events), etc.

## Attribute Synchronization

When an event is triggered on a form element (`input`, `textarea`, `select`), `htag` automatically synchronizes the client-side state back to the Python `Tag` instance **before** the callback is executed.

The following attributes are synchronized:
- `["value"]`: The current text or selected value.
- `["checked"]`: The boolean state (for `checkbox` and `radio`).
- `["name"]`: The element's name.

This means you can reliably access `e.target["value"]` or `e.target["checked"]` in your event handlers, and they will always match the browser's current state.

## Automatic Binding (Magic Bind)

`htag` automatically synchronizes the state of input elements without requiring explicit event handlers.

When you use an `<input>`, `<textarea>`, or `<select>`, `htag` injects an `oninput` event that updates the component's `value` attribute in real-time on the server.

```python
class MyForm(Tag.App):
    def init(self) -> None:
        # No '_oninput' needed, it's automatic!
        self.entry = Tag.input(_value="Initial")
        self <= self.entry
        self <= Tag.button("Show", _onclick=lambda e: self.add(f"Value is: {self.entry['value']}"))

Form Handling (Submit)

When you use a Tag.form, the submit event (triggered by e.g. ["onsubmit"]) receives an Event object where event.value is a dictionary containing all named form fields (_name="fieldname").

You can access these fields directly on the event object using square brackets for convenience:

class MyForm(Tag.form):
    def init(self) -> None:
        # Use '_name' to define the key in the form data
        self <= Tag.input(_name="user", _value="bob")
        self <= Tag.input(_name="email", _value="bob@mail.com")
        self <= Tag.input(_type="submit")
        self["onsubmit"] = self.post

    @prevent
    def post(self, e: Any) -> None:
        # e.value is {'user': '...', 'email': '...'}
        # For checkboxes in a form, e.value['mycheck'] is the boolean state
        print(f"Submitting: {e['user']} ({e['email']})")

htag fully supports asyncio. You can define callbacks as async def:

import asyncio from typing import Any

async def my_async_callback(e: Any) -> None: await asyncio.sleep(1) e.target.add("Done!")

## UI Streaming (Generators)

For long-running tasks that need to update the UI multiple times, you can use generators:

from typing import Any, Generator

def my_generator(e: Any) -> Generator:
    e.target.add("Starting...")
    yield # Triggers a UI update to the client

    import time
    time.sleep(2)
    e.target.add("Halfway...")
    yield

    time.sleep(2)
    e.target.add("Finished!")

Async Generators

htag also supports async for generators for asynchronous UI streaming.

from typing import Any, AsyncGenerator

async def my_async_gen(e: Any) -> AsyncGenerator: e.target.add("Fetching...") yield await asyncio.sleep(2) e.target.add("Data ready!")

> [!TIP]
> Use generators for any operation that takes more than 100ms to keep the UI responsive and provide feedback to the user.


## Lifecycle Hooks

`htag` components provide three key lifecycle methods:

- **`init(**kwargs)`**: Called once when the component is created.
- **`on_mount()`**: Fired when the component is attached to the app. In `WebApp`, this is **re-triggered on every page refresh (F5)**, allowing you to reset volatile UI state (e.g., clearing status messages) while preserving the backend session instance.
- **`on_unmount()`**: Fired when the component is removed from the tree. In `WebApp`, it is **also called before every page refresh (F5)**, allowing you to properly clean up resources (e.g., cancelling background tasks) before the view is reset.

### Generators in Lifecycle Hooks
Both `on_mount` and `on_unmount` fully support `yield` (standard or async). `htag` intelligently queues `on_mount` updates until the client is ready, and ensures `on_unmount` broadcasts are sent before the component is discarded.

## Event Decorators

- `@prevent`: Calls `event.preventDefault()` in the browser.
- `@stop`: Calls `event.stopPropagation()` in the browser.

These decorators can be used directly on class methods as decorators, or applied to any callable (including bound methods like `self.my_method`) at runtime.

```python
from htag import prevent, stop

class MyTag(Tag.form):
    def init(self):
        # Applied at runtime on a bound method
        self["onsubmit"] = prevent(self.handle_submit)

    def handle_submit(self, e):
        # Form won't reload the page
        pass

Simple Events & HashChange

htag supports "simple events" where you can pass primitive values or custom objects from JavaScript back to Python.

HashChange Event

When you set self["onhashchange"], the Python callback receives an Event object with newURL and oldURL attributes.

class App(Tag.App):
    def init(self):
        self["onhashchange"] = self.on_hash

    def on_hash(self, e):
        print(f"Navigated to: {e.newURL}")

Custom Simple Events

You can trigger custom events from JavaScript with any data using the global htag_event function:

# In Python
tag["oncustom"] = lambda e: print(f"Received value: {e.value}")

# In JavaScript
htag_event('tag_id', 'custom', 'some string')
htag_event('tag_id', 'custom', {key: 'value'})

If a primitive value is passed, it is available as e.value in Python. If an object is passed, its properties are mapped directly to the Event object.

Client-side JavaScript

You can execute arbitrary JavaScript from the server using call_js():

class MyTag(Tag.div):
    def boom(self, e):
        self.call_js("alert('BOOM!')")

← Components | Reactivity & State → | Next: Runners →