Data Binding

RxHTML connects to Adama documents via WebSocket and keeps the UI in sync with server data automatically. This chapter covers establishing connections, displaying data, and sending messages — the bread and butter of building anything with RxHTML.

The Connection Element

The <connection> element establishes a WebSocket connection to an Adama document:

<forest>
  <page uri="/">
    <connection space="my-space" key="my-key">
      <!-- Everything inside has access to document data -->
      <h1><lookup path="title" /></h1>
    </connection>
  </page>
</forest>

Connection Modes

Connect by space and key:

<connection space="my-space" key="my-key">
  <!-- Direct document reference -->
</connection>

Or connect using domain mapping (handy for multi-tenant setups):

<connection use-domain>
  <!-- Document determined by the domain -->
</connection>

Connection Attributes

Attribute Purpose
space The Adama space name
key The document key within the space
use-domain Use domain mapping instead of space/key
name Name this connection for reference elsewhere
identity Override the identity for authorization
redirect Redirect to this page on connection failure
keep-open Keep rendering even if connection drops

Connection Status

Children of <connection> only render when connected. You can handle the disconnected state too:

<connection space="my-space" key="my-key">
  <div>Connected! Here is your data...</div>
  <div rx:else>Connecting...</div>
</connection>

Named Connections

Name a connection so you can reference it from elsewhere on the page:

<connection space="my-space" key="my-key" name="main">
  <!-- Main content -->
</connection>

<!-- Elsewhere in the page -->
<pick name="main">
  <!-- Uses the same connection -->
  <lookup path="some_field" />
</pick>

The <pick> element reuses an existing named connection. This avoids opening a second WebSocket just to read from the same document in a different part of the page.

Connection Status Element

Display connection status without messing with rendering of children:

<connection-status name="main">
  <span>Connected</span>
  <span rx:disconnected>Reconnecting...</span>
</connection-status>

Displaying Data

The Lookup Element

Display a value from the document as text:

<connection space="my-space" key="my-key">
  <h1><lookup path="title" /></h1>
  <p>By <lookup path="author" /></p>
  <span><lookup path="views" /> views</span>
</connection>

Lookup Transforms

Transform values before display:

<lookup path="created_at" transform="time_ago" />
<lookup path="name" transform="upper" />
<lookup path="file_size" transform="size_bytes" />

Available transforms:

Transform Description
trim Trim whitespace
upper Convert to uppercase
lower Convert to lowercase
time_ago Convert datetime to "3 hours ago" format
time Format time for display
size_bytes Format bytes as KB, MB, GB
jsonify Convert to JSON string
principal.agent Extract agent from principal
principal.authority Extract authority from principal
vulgar_fraction Convert decimal to unicode fraction

Lookup Refresh

For time-based transforms, you probably want periodic refreshes:

<lookup path="created_at" transform="time_ago" refresh="60000" />

The refresh attribute is in milliseconds. So 60000 = update the "3 hours ago" text every minute.

Attribute Binding

Bind document values to HTML attributes using curly braces:

<img src="{profile_image_url}" alt="Profile for {username}" />
<a href="/user/{user_id}">View Profile</a>

Conditional Attribute Values

Attributes support conditional expressions, which is where things get interesting:

<!-- Boolean toggle -->
<div class="[is_active]active[/]">
  Shows "active" class when is_active is true
</div>

<!-- Boolean with else branch -->
<div class="[is_active]active[#]inactive[/]">
  Shows "active" or "inactive" based on is_active
</div>

<!-- Value comparison -->
<div class="[status=published]public[#]draft[/]">
  Shows "public" if status equals "published", else "draft"
</div>

<!-- Combine with literal text -->
<div class="card [highlighted]card-highlighted[/]">
  Always has "card" class, conditionally has "card-highlighted"
</div>

Trusted HTML

Render HTML content directly (be careful with this one):

<trusted-html path="html_content" />

This sets innerHTML directly. Only use it with content you control; otherwise you're asking for trouble.

Dynamic Titles

Set the page title reactively:

<title value="Messages for {username}" />

Scoping into Data

Scope into Objects

Navigate into nested objects so you don't have to write long paths everywhere:

<div rx:scope="user">
  <lookup path="name" />
  <lookup path="email" />
</div>

Equivalent to:

<lookup path="user/name" />
<lookup path="user/email" />

Scope and View State Expansion

By default, scoping only affects data paths. Use rx:expand-view-state to also scope the view state:

<div rx:scope="settings" rx:expand-view-state>
  <!-- view:field here refers to view:settings/field -->
</div>

This matters when each item needs its own isolated view state — think per-row UI toggles in a table.

Forms and Sending Messages

Basic Form Submission

Send messages to Adama channels using forms:

<form rx:action="send:create_task">
  <input type="text" name="title" placeholder="Task title" />
  <button type="submit">Create</button>
</form>

The form fields get collected into a message matching the channel's expected type. This form sends to the create_task channel with something like { title: "..." }. No serialization code, no fetch calls — just a form.

Form Actions

Action Purpose
send:channel_name Send message to channel
domain:sign-in Authenticate via domain document
domain:sign-in-reset Authenticate and change password
document:sign-in Authenticate to specific document
document:put HTTP PUT to document web endpoint
copy:path Copy form values to view state
custom:verb Run custom JavaScript handler

Sending with Checkboxes

For toggle operations, combine a form with click events:

<form rx:action="send:toggle_task">
  <input type="hidden" name="task_id" value="{id}" />
  <input type="checkbox" rx:checked="done" />
  <button type="submit">Toggle</button>
</form>

Success and Failure Handling

Handle form submission results:

<form rx:action="send:create_task"
      rx:success="goto:/tasks"
      rx:failure="raise:show_error">
  <input type="text" name="title" />
  <button type="submit">Create</button>

  <div rx:if="view:show_error">
    Something went wrong. Please try again.
  </div>
</form>

Password Handling

RxHTML treats password fields specially for security:

  • Fields named password are never sent to the server in plain text
  • For channel messages, passwords are hashed before sending
  • For authentication, passwords flow through the auth mechanism

Special password field names:

Field Name Purpose
password Primary password (hashed before sending)
new_password New password for password changes
confirm-password UI validation only, stripped before send
confirm-new_password UI validation only, stripped before send

View State Synchronization

Sync Input to View State

Synchronize input values to the view state:

<input type="text" rx:sync="search_filter" />

As the user types, view:search_filter updates. Your Adama code can use this to filter results in real time:

record Item {
  public string title;
}
table<Item> _items;
view string search_filter;

bubble filtered_results = iterate _items
  where @viewer.search_filter == "" || title.contains(@viewer.search_filter);

This is the feedback loop in action — user types, view state updates, Adama recomputes the bubble, delta flows back, UI refreshes. All without a single line of JavaScript.

Debounce Updates

Control how often sync updates get sent:

<input type="text" rx:sync="search" rx:debounce="300" />

Updates fire at most every 300 milliseconds. Without this, you'd hammer the server on every keystroke.

URL Parameter Sync

Synchronize view state with URL parameters:

<view-state-params sync:page="page" sync:filter="filter" />

This keeps view:page and view:filter in sync with ?page=...&filter=... in the URL. Good for shareable/bookmarkable state.

Event Handling

RxHTML has a command language for handling events:

<button rx:click="toggle:show_details">Toggle Details</button>
<button rx:click="set:tab='settings'">Settings Tab</button>
<button rx:click="inc:counter">+1</button>

Available Commands

Command Effect
toggle:path Toggle boolean in view state
set:path=value Set value in view state
raise:path Set boolean to true
lower:path Set boolean to false
inc:path Increment number
dec:path Decrement number
goto:uri Navigate to URI
fire:channel Send empty message to channel
submit Submit the containing form
reset Reset the containing form

Multiple Commands

Chain commands with spaces:

<button rx:click="set:tab='main' lower:show_modal">
  Close and Go to Main
</button>

String Values

Use single quotes for string values with spaces:

<button rx:click="set:title='Hello World'">Set Title</button>

Event Types

Attribute Event
rx:click Element clicked
rx:mouseenter Mouse entered element
rx:mouseleave Mouse left element
rx:change Input value changed
rx:blur Input lost focus
rx:focus Input gained focus
rx:check Checkbox checked
rx:uncheck Checkbox unchecked
rx:load Element was rendered
rx:success Form submission succeeded
rx:failure Form submission failed

Now go learn about advanced patterns — conditionals, iteration, and the escape hatches for when the declarative approach isn't enough.

Previous Fundamentals
Next Advanced