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.x`, `e.y` (for mouse events), etc.
## 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': '...'}
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.
## Event Decorators
- `@prevent`: Calls `event.preventDefault()` in the browser.
- `@stop`: Calls `event.stopPropagation()` in the browser.
```python
from htag import prevent, stop
@prevent
def handle_submit(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():