Ragot

Modules

Non-visual browser lifecycle — server calls, sockets, timers, and state management.

Modules

A Module owns non-visual browser lifecycle: server communication, socket events, timers, store subscriptions, and child Components. It's the brain of an interactive page.

Basic shape

from sprag import Module

class TodoModule(Module):
    def on_start(self):
        self.delegate(self.element, "click", "[data-role='add']", self.on_add)
        self.delegate(self.element, "submit", "form", self.on_submit)

    def on_submit(self, event, target):
        event.prevent_default()
        text = self.element.querySelector("[name='text']").value
        self.call_action("add_item", {"text": text}).then(self.on_added)

    def on_add(self, event, target):
        event.prevent_default()
        self.set_state({"adding": True})

Constructor

__init__ supports field assignments, conditionals, local variables, and method calls. The only restriction is that self.state and self.screen are owned by the runtime and cannot be assigned directly.

class GameModule(Module):
    def __init__(self):
        super().__init__()
        self.timer_id = None
        self.config = {"difficulty": "normal", "rounds": 5}
        if some_condition:
            self.mode = "advanced"

Heavy setup (DOM access, event listeners, server calls) belongs in on_start().

Lifecycle

MethodWhen
on_start()After hydration, Module is attached to the DOM
on_stop()Before the page tears down

State

# Read current state
count = self.state.get("count", 0)

# Update state (triggers Component re-render)
self.set_state({"count": count + 1})

# Watch for state changes
self.watch_state(lambda state: print("state changed:", state))

Server calls

call_action(action, payload)

Calls a server @action and returns a Promise-like result:

def on_increment(self, event, target):
    self.call_action("increment", {"count": self.state["count"]}).then(self.on_result)

def on_result(self, result):
    self.set_state(result.value)

Use result.value to read the action payload and update Module state explicitly.

Returns a Promise when you need to handle the response:

def on_save(self, event, target):
    result = self.call_action("save", {"text": self.state["text"]})
    result.then(self._on_saved)

DOM access

  • self.element — the DOM node this Module is attached to (passed from hydrate())

Child Components

Most interactive pages should let hydrate(...) wire Module/Component ownership for you:

# web.py
class MyScreen(Screen):
    modules = [SidebarModule]

    def render(self, data):
        module = self.module(SidebarModule)
        module.set_state(data)
        return hydrate(SidebarComponent, module=module)

For advanced ownership patterns, adopt_component(...) is also real on the underlying Ragot/SPRAG Module surface, but hydrate(...) is still the default and safest authoring path.

Sockets

def on_start(self):
    # Listen for socket events
    self.on_socket("items_changed", self._on_items)

    # Emit a socket event
    self.emit_socket("join", {"room": "lobby"})

    # Join/leave a topic (room)
    self.join_topic("room:lobby")

def on_stop(self):
    self.leave_topic("room:lobby")

def _on_items(self, data):
    self.call_action("get_items", {}).then(self._on_items_refetched)

def _on_items_refetched(self, result):
    self.set_state(result.value or {})

Refetch shorthand

def on_start(self):
    # Automatically call "get_items" when "items_changed" arrives
    self.refetch_on_socket("items_changed", action="get_items")

Uploads

# Form-based upload
def on_submit(self, event, target):
    event.prevent_default()
    self.upload_form("avatar", event, self.on_progress)

# Programmatic upload
def on_drop(self, event, target):
    event.prevent_default()
    file = event.dataTransfer.files[0]
    self.upload("process", file, {"resize": True}, self.on_progress)

def on_progress(self, progress):
    self.set_state({"upload_percent": progress.percent})

Navigation

def on_click(self, event, target):
    event.prevent_default()
    self.navigate("/other-page")

Timers

def on_start(self):
    self.interval(self._poll, 30)   # Every 30 seconds
    self.timeout(self._delayed, 5)  # After 5 seconds

Auto-cleaned on on_stop().

Store subscriptions

from sprag import store

counter_store = store("counter", initial={"count": 0})

class MyModule(Module):
    def on_start(self):
        self.subscribe(counter_store, self._on_store)

    def _on_store(self, state, meta, store):
        self.set_state({"count": state["count"]})

Auto-cleaned on on_stop().