Live views

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

It suits anything where the server should hold the truth: a register, a dashboard, a checkout. It isn’t for collaborative editing: two people typing in one field overwrite each other, in the order they arrive.

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 says who may open it, in the rules language.
  • mount makes the first state; ctx says who opened the view.
  • render draws 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");

Elements say which events they send:

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

The payload carries the element’s value, a form’s fields, and any sluurp-value-* attributes. sluurp-ignore on an element keeps its children as they are across updates, for a component the browser owns.

Why so little goes over the wire

An html template is split by JavaScript itself into the parts that never change and the values put into them. Only the values are compared between renders, and only those that differ are sent. A counter going from 41 to 42 sends 42.

Signing in is the ordinary browser client’s, so a view sees the same person the REST API does.