Dev.to · 4 min read

Forms, payloads, and live inputs in Fitz LiveViews

Forms, payloads, and live inputs in Fitz LiveViews

TL;DR — Events in Fitz LiveViews carry data three ways: a click payload (data-flv-value-*) tags a button with the value it should send; a form submit (data-flv-submit) reads the form's named inputs; and a live value (@input / @change) delivers a control's current value in payload["value"]. All three land in the same place — a payload map your handler reads. This post builds a live name list (add / remove / count) that runs both server-rendered and as WebAssembly. (Part 3 of the FitzLiveViews series.) Parts 1 and 2 covered the pitch and the counter. A counter only reads +1/-1 — no data flows in. Real UIs take input: text, selections, form fields. Here's how that data reaches your handlers. The payload Every event handler has a payload in scope — a Map. The three mechanisms below all fill it; your handler reads it with payload["key"] (guard with payload.has("key")): 1. Click payload — a button that carries a value Tag any element with data-flv-value-="{expr}", and when a data-flv-click on it (or an ancestor) fires, that value rides along: × event remove() { if (payload.has("item")) { let target = payload["item"] names = names.filter(fn(it) => it != target) } } The delete button knows which row it is because the row's value is stamped on it. No IDs threaded through a callback, no closure capture. 2. Form submit — the whole form at once data-flv-submit="handler" on a reads each named input into the payload on submit; data-flv-clear resets a field afterward: Add event add() { if (payload.has("item")) { let n = payload["item"] if (n != "") { names.push(n) } } } payload["item"] is the input's value at submit time. No preventDefault, no FormData, no fetch. 3. Live value — @input / @change For a control that reports as you type or on selection, @input (every keystroke) and @change (on blur/selection) deliver the current value in payload["value"]: … event on_name() { name = payload["value"] } The same payload["value"] covers , , and . On the server-rendered target this is the classic data-flv-change attribute; on the client-WASM target it's the @input / @change decorator — same payload, same handler, so one .fitzv serves both. Putting it together — a live name list component NameList { state { names: List = ["Ada", "Grace", "Margaret"] } event add() { if (payload.has("item")) { let n = payload["item"] if (n != "") { names.push(n) } } } event remove() { if (payload.has("item")) { let target = payload["item"] names = names.filter(fn(it) => it != target) } } Add {#for it in names} {it} × {/for} {names.len()} total } {#for it in names} iterates; {it} interpolates each item; the remove button stamps its own value. Add a name, remove a row, watch the count — no client code, no API. Wire it into a main.fitz (the same shape as the counter in part 2) and it runs: from fitz_liveviews import Html, html, live_layout, html_response, LiveFrame, diff_html, component, dispatch_component_events, flv_register from NameList import NameList, NameList_render, NameList_add, NameList_remove @get("/") fn page() -> Response { return html_response(live_layout("/live/names", "names-app", component("NameList", "root"))) } @ws("/live/names") async fn socket(ws: WsConn) { let last = component("NameList", "root").raw loop { let frame = ws.recv()? let _ = dispatch_component_events(frame) let new_html = component("NameList", "root").raw ws.send(LiveFrame { html: new_html, patches: diff_html(last, new_html) })? last = new_html } } @server(3000) fn main() => 0 fitz run, open http://localhost:3000, add and remove names. (I ran exactly this — the page renders the seeded list, the count, and the wired form/buttons.) The client-WASM build of the same list is running in the live gallery — add/remove/count entirely in your browser, offline. Two honest caveats Live text inputs re-mount. The current rendering model is dirty-flag + naive re-render: a state change rebuilds the component's DOM. For a @change that's invisible; for a live text @input, the field re-mounts each keystroke — the value re-binds via value="{name}", but the caret jumps to the end. Fine-grained reactivity (patch in place) is the next rendering-model step, and it's what fixes this. The value always reaches the handler reliably; that's what @input guarantees today. String methods on WASM — now supported. A case-insensitive filter (names.filter(fn(x) => q == "" or x.lower().contains(q.lower()))) used to be server-rendered only; as of Fitz core v0.29.8 the string methods (.lower / .contains / …) and logical and/or compile on the client-WASM target too, so the filter runs offline. The only remaining wrinkle is the caret caveat above for the filter box itself while you type. What's next in this series #4+ — Building the flagship. A complete admin panel in Fitz + Fitz LiveViews: cookie-based auth (Argon2id + JWT), live DataGrids querying Postgres over WebSockets, the packaged UI library, i18n, and a one-command Docker setup. If typed events without a client framework sound good, star the repo. Next: a real app.

This is a summary aggregated from Dev.to. Read the complete article on the original site:

Read full article at Dev.to

More Cloud & DevOps News