# Spry Framework - Important Details ## Component Basics ### Template DOM is NOT Persisted - The template is fresh on every request - Any state set via setters (like `title`, `completed`) is NOT available in `handle_action()` - Always use `prepare()` to fetch data from store and set up the template ### The `prepare()` Method - Called automatically before serialization - Use this to fetch data from stores and populate the template - Centralizes data fetching logic in one place ```vala public override async void prepare() throws Error { var item = store.get_by_id(_item_id); if (item == null) return; this["title"].text_content = item.title; this["button"].text_content = item.completed ? "Undo" : "Done"; } ``` ### The `prepare_once()` Method - Called only once before the first `prepare()` call - Useful for one-time initialization that should not repeat on every render - Runs before `prepare()` in the same request lifecycle ```vala public override async void prepare_once() throws Error { // One-time setup, e.g., loading initial data initial_data = yield fetch_initial_data(); } ``` ### The `handle_action()` Method - Called when HTMX requests trigger an action - Modify state in stores, then let `prepare()` handle template updates - For delete with `hx-swap="delete"`, just return (no content needed) ```vala public async override void handle_action(string action) throws Error { var id = get_id_from_query_params(); _item_id = id; // Set for prepare() switch (action) { case "Toggle": store.toggle(id); break; // prepare() will be called automatically case "Delete": store.remove(id); return; // HTMX removes element via hx-swap="delete" } } ``` ## Real-Time Updates with Continuations (SSE) ### Overview The continuation feature allows a Component to send real-time updates to the client via Server-Sent Events (SSE). This is useful for: - Long-running task progress reporting - Real-time status updates - Live data streaming ### The `continuation()` Method Override `continuation(ContinuationContext context)` to send SSE events: ```vala public async override void continuation(ContinuationContext continuation_context) throws Error { for (int i = 0; i <= 100; i += 10) { percent = i; status = @"Processing... $(i)%"; // Send fragment updates to the client yield continuation_context.send_fragment("progress", "progress-bar"); yield continuation_context.send_fragment("status", "status"); Timeout.add(500, () => { continuation.callback(); return false; }); yield; } status = "Complete!"; yield continuation_context.send_fragment("status", "status"); } ``` ### The `continuation_canceled()` Method Called when the client disconnects from the SSE stream: ```vala public async override void continuation_canceled() throws Error { // Clean up resources when client disconnects cleanup_task(); } ``` ### ContinuationContext API | Method | Description | |--------|-------------| | `send_fragment(event_type, sid)` | Send a fragment (by `sid`) as an SSE event with the given event type | | `send_json(event_type, node)` | Send JSON data as an SSE event | | `send_string(event_type, data)` | Send raw string data as an SSE event | | `send_full_update(event_type)` | Send the entire component document | | `send_event(event)` | Send a custom `SseEvent` | ### The `spry-continuation` Attribute Add `spry-continuation` to an element to enable SSE: ```vala public override string markup { get { return """
` and `` tags do NOT escape their contents. Manually escape:
```vala
// Wrong
// Correct
<button spry-action=":Toggle">Click</button>
```
## SpryModule
Automatically registers `ComponentEndpoint` at `/_spry/{component-id}/{action}`:
```vala
application.add_module();
```
This enables the declarative `spry-action` attributes to work without manual endpoint registration.
## Declarative Child Components with ``
### When to Use `` vs ``
- **` `** - For single, known child components
- **` `** - For dynamic lists (multiple items from data)
### Declaring Child Components
Use `` in markup for declarative composition:
```vala
class TodoPage : PageComponent {
public override string markup { get {
return """
""";
}}
}
```
### Accessing Child Components with `get_component_child()`
Use `get_component_child(sid)` in `prepare()` to access and configure child components:
```vala
class TodoPage : PageComponent {
private TodoStore todo_store = inject();
private ComponentFactory factory = inject();
public override async void prepare() throws Error {
// Get child component and configure it
var todo_list = get_component_child("todo-list");
// Populate the list (which still uses spry-outlet for dynamic items)
var items = new Series();
todo_store.all().iterate((item) => {
var component = factory.create();
component.item_id = item.id;
items.add(component);
});
todo_list.set_items(items);
}
}
```
## PageComponent - Combining Component and Endpoint
`PageComponent` is a base class that acts as both a `Component` AND an `Endpoint`. This eliminates the need for separate endpoint classes:
```vala
// Before: Separate endpoint class
class HomePageEndpoint : Object, Endpoint {
private ComponentFactory factory = inject();
public async HttpResult handle_request(HttpContext context, RouteContext route) throws Error {
var page = factory.create();
return yield page.to_result();
}
}
// After: PageComponent handles both roles
class TodoPage : PageComponent {
public override string markup { get { return "..."; } }
public override async void prepare() throws Error { /* ... */ }
}
```
## SpryConfigurator - Component and Page Registration
Use `SpryConfigurator` to register components and pages in a structured way:
```vala
// Get the configurator
var spry_cfg = application.configure_with();
// Register child components (transient lifecycle)
spry_cfg.add_component();
spry_cfg.add_component();
spry_cfg.add_component();
spry_cfg.add_component();
spry_cfg.add_component();
// Register pages (scoped lifecycle, acts as Endpoint)
spry_cfg.add_page(new EndpointRoute("/"));
// Other endpoints still use add_endpoint
application.add_endpoint(new EndpointRoute("/api/todos"));
```
### Registration Methods
| Method | Lifecycle | Use Case |
|--------|-----------|----------|
| `add_component()` | Transient | Child components created via factory |
| `add_page(route)` | Scoped | Page components that act as endpoints |
| `add_template(prefix)` | Transient | Page templates for layout wrapping |