From cf6f62a939eb250d0d0fab76a1624a87ee474291 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Mon, 7 Sep 2026 21:03:17 +1200 Subject: [PATCH 1/2] Document form handling Signed-off-by: Samuel Williams Assisted-By: devx/7ef83274-928f-499d-b668-48449b1ff60e --- context/getting-started.md | 47 +++++++++++++++++++++++++++++++- guides/getting-started/readme.md | 47 +++++++++++++++++++++++++++++++- 2 files changed, 92 insertions(+), 2 deletions(-) diff --git a/context/getting-started.md b/context/getting-started.md index 2ad037a..082d267 100644 --- a/context/getting-started.md +++ b/context/getting-started.md @@ -61,13 +61,58 @@ Render the tag in your view layer: #{ClickCounter.root.to_html} ~~~ +## Handling Forms + +Forms can forward submissions to their server-side view without navigating away from the page. The {ruby Live::Element#forward_form_event} helper prevents the normal submission, serializes the successful form controls, and sends them as part of the event. + +~~~ ruby +class ContactForm < Live::View + def handle(event) + return unless event[:type] == "submit" + + fields = event[:formData].to_h + @data[:status] = "Received: #{fields.fetch("message")}" + + update! + end + + def render(builder) + builder.tag :form, action: "/contact", method: "post", onsubmit: forward_form_event do + builder.tag :textarea, name: "message" do + builder.text("") + end + + builder.tag :button, type: "submit", name: "action", value: "send" do + builder.text("Send") + end + + if status = @data[:status] + builder.tag :p do + builder.text(status) + end + end + end + end +end +~~~ + +The `event[:formData]` value is an array of name-value pairs, preserving repeated controls with the same name. Convert it to a hash only when the form uses unique control names. The submitting button's name and value are included when available. + +The form's normal `action` and `method` still provide a fallback when JavaScript is unavailable. The application is responsible for handling that HTTP endpoint. + +Render the form in the same way as any other live view: + +~~~ ruby +#{ContactForm.root.to_html} +~~~ + ## Implementing the Server On the server side, in the controller layer, we need to handle the incoming WebSocket request: ~~~ ruby # This controls which classes can be created by the client tags: -RESOLVER = Live::Resolver.allow(ClickCounter) +RESOLVER = Live::Resolver.allow(ClickCounter, ContactForm) # At the same path as the request: run do |env| diff --git a/guides/getting-started/readme.md b/guides/getting-started/readme.md index 2ad037a..082d267 100644 --- a/guides/getting-started/readme.md +++ b/guides/getting-started/readme.md @@ -61,13 +61,58 @@ Render the tag in your view layer: #{ClickCounter.root.to_html} ~~~ +## Handling Forms + +Forms can forward submissions to their server-side view without navigating away from the page. The {ruby Live::Element#forward_form_event} helper prevents the normal submission, serializes the successful form controls, and sends them as part of the event. + +~~~ ruby +class ContactForm < Live::View + def handle(event) + return unless event[:type] == "submit" + + fields = event[:formData].to_h + @data[:status] = "Received: #{fields.fetch("message")}" + + update! + end + + def render(builder) + builder.tag :form, action: "/contact", method: "post", onsubmit: forward_form_event do + builder.tag :textarea, name: "message" do + builder.text("") + end + + builder.tag :button, type: "submit", name: "action", value: "send" do + builder.text("Send") + end + + if status = @data[:status] + builder.tag :p do + builder.text(status) + end + end + end + end +end +~~~ + +The `event[:formData]` value is an array of name-value pairs, preserving repeated controls with the same name. Convert it to a hash only when the form uses unique control names. The submitting button's name and value are included when available. + +The form's normal `action` and `method` still provide a fallback when JavaScript is unavailable. The application is responsible for handling that HTTP endpoint. + +Render the form in the same way as any other live view: + +~~~ ruby +#{ContactForm.root.to_html} +~~~ + ## Implementing the Server On the server side, in the controller layer, we need to handle the incoming WebSocket request: ~~~ ruby # This controls which classes can be created by the client tags: -RESOLVER = Live::Resolver.allow(ClickCounter) +RESOLVER = Live::Resolver.allow(ClickCounter, ContactForm) # At the same path as the request: run do |env| From 40504fa7f312efe4e9636003bb9c2d56abd652f7 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Mon, 7 Sep 2026 21:05:34 +1200 Subject: [PATCH 2/2] Use Ruby-style form data key Signed-off-by: Samuel Williams Assisted-By: devx/7ef83274-928f-499d-b668-48449b1ff60e --- context/getting-started.md | 4 ++-- guides/getting-started/readme.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/context/getting-started.md b/context/getting-started.md index 082d267..b278e44 100644 --- a/context/getting-started.md +++ b/context/getting-started.md @@ -70,7 +70,7 @@ class ContactForm < Live::View def handle(event) return unless event[:type] == "submit" - fields = event[:formData].to_h + fields = event[:form_data].to_h @data[:status] = "Received: #{fields.fetch("message")}" update! @@ -96,7 +96,7 @@ class ContactForm < Live::View end ~~~ -The `event[:formData]` value is an array of name-value pairs, preserving repeated controls with the same name. Convert it to a hash only when the form uses unique control names. The submitting button's name and value are included when available. +The `event[:form_data]` value is an array of name-value pairs, preserving repeated controls with the same name. Convert it to a hash only when the form uses unique control names. The submitting button's name and value are included when available. The form's normal `action` and `method` still provide a fallback when JavaScript is unavailable. The application is responsible for handling that HTTP endpoint. diff --git a/guides/getting-started/readme.md b/guides/getting-started/readme.md index 082d267..b278e44 100644 --- a/guides/getting-started/readme.md +++ b/guides/getting-started/readme.md @@ -70,7 +70,7 @@ class ContactForm < Live::View def handle(event) return unless event[:type] == "submit" - fields = event[:formData].to_h + fields = event[:form_data].to_h @data[:status] = "Received: #{fields.fetch("message")}" update! @@ -96,7 +96,7 @@ class ContactForm < Live::View end ~~~ -The `event[:formData]` value is an array of name-value pairs, preserving repeated controls with the same name. Convert it to a hash only when the form uses unique control names. The submitting button's name and value are included when available. +The `event[:form_data]` value is an array of name-value pairs, preserving repeated controls with the same name. Convert it to a hash only when the form uses unique control names. The submitting button's name and value are included when available. The form's normal `action` and `method` still provide a fallback when JavaScript is unavailable. The application is responsible for handling that HTTP endpoint.