Live views

A live view keeps its state on the server and renders it there. The browser sends user events and gets back only the values that changed, often a few bytes.

Use it when the server should own the state: a register, a dashboard, a checkout. It isn’t meant for collaborative editing: two people typing in the same field overwrite each other, last write wins.

A view

A view is views/<name>.js in the app:

views/counter.js
export const rule = "@request.auth.id != null";

export function mount(ctx) {
  return { count: 0 };
}

export function render(state, html) {
  return html`<button sluurp-click="bump">Clicked ${state.count} times</button>`;
}

export function handle(event, payload, state, ctx) {
  if (event === "bump") return { ...state, count: state.count + 1 };
  return state;
}
  • rule: who may open it, in the rules language.
  • mount: returns the initial state; ctx has the user who opened the view.
  • render: renders the state with the html tag.
  • handle: takes an event and returns the next state.

On the page

app.ts
import { mount } from "sluurp/live";
await mount("#counter", "counter");

Attributes bind DOM events to view events:

<button sluurp-click="bump">+1</button>
<input sluurp-input="search" name="q">
<form sluurp-submit="save">…</form>

The payload includes the element’s value, a form’s fields, and any sluurp-value-* attributes. Add sluurp-ignore to an element to leave its children untouched across updates, e.g. for a client-side widget.

Why so little goes over the wire

A tagged html template is already split by JavaScript into static strings and dynamic values. Only the values are diffed between renders, and only changed ones are sent. A counter going from 41 to 42 sends 42.

Auth is the regular browser client’s, so a view sees the same user the REST API does.