diff --git a/examples/form.rs b/examples/form.rs new file mode 100644 index 000000000..5f0c992f5 --- /dev/null +++ b/examples/form.rs @@ -0,0 +1,26 @@ +//! Forms +//! +//! Dioxus forms deviate slightly from html, automatically returning all named inputs +//! in the "values" field + +use dioxus::prelude::*; + +fn main() { + dioxus::desktop::launch(app); +} + +fn app(cx: Scope) -> Element { + cx.render(rsx! { + div { + h1 { "Form" } + form { + onsubmit: move |ev| println!("Submitted {:?}", ev.values), + oninput: move |ev| println!("Input {:?}", ev.values), + input { r#type: "text", name: "username" } + input { r#type: "text", name: "full-name" } + input { r#type: "password", name: "password" } + button { "Submit the form" } + } + } + }) +} diff --git a/packages/html/src/events.rs b/packages/html/src/events.rs index b9bcecd4f..8067e6314 100644 --- a/packages/html/src/events.rs +++ b/packages/html/src/events.rs @@ -3,6 +3,8 @@ use dioxus_core::exports::bumpalo; use dioxus_core::*; pub mod on { + use std::collections::HashMap; + use super::*; macro_rules! event_directory { ( $( @@ -484,6 +486,7 @@ pub mod on { #[derive(Debug)] pub struct FormData { pub value: String, + pub values: HashMap, /* DOMEvent: Send + SyncTarget relatedTarget */ } diff --git a/packages/interpreter/src/interpreter.js b/packages/interpreter/src/interpreter.js index dbcb73a96..300d12001 100644 --- a/packages/interpreter/src/interpreter.js +++ b/packages/interpreter/src/interpreter.js @@ -216,7 +216,12 @@ export class Interpreter { } } // walk the tree to find the real element - while (realId == null && target.parentElement != null) { + while (realId == null) { + // we've reached the root we don't want to send an event + if (target.parentElement === null) { + return; + } + target = target.parentElement; realId = target.getAttribute(`data-dioxus-id`); } @@ -224,13 +229,33 @@ export class Interpreter { shouldPreventDefault = target.getAttribute( `dioxus-prevent-default` ); + let contents = serialize_event(event); + if (shouldPreventDefault === `on${event.type}`) { - // event.preventDefault(); + event.preventDefault(); } if (event.type == "submit") { - // event.preventDefault(); + event.preventDefault(); } + + if (target.tagName == "FORM") { + for (let x = 0; x < target.elements.length; x++) { + let element = target.elements[x]; + let name = element.getAttribute("name"); + if (name != null) { + if (element.getAttribute("type") == "checkbox") { + // @ts-ignore + contents.values[name] = element.checked ? "true" : "false"; + } else { + // @ts-ignore + contents.values[name] = + element.value ?? element.textContent; + } + } + } + } + if (realId == null) { return; } @@ -255,7 +280,8 @@ export class Interpreter { } } } -function serialize_event(event) { + +export function serialize_event(event) { switch (event.type) { case "copy": case "cut": @@ -326,6 +352,7 @@ function serialize_event(event) { } return { value: value, + values: {}, }; } case "click": diff --git a/packages/web/Cargo.toml b/packages/web/Cargo.toml index 9145fed8b..57fc797df 100644 --- a/packages/web/Cargo.toml +++ b/packages/web/Cargo.toml @@ -42,6 +42,7 @@ features = [ "HtmlInputElement", "HtmlSelectElement", "HtmlTextAreaElement", + "HtmlFormElement", "EventTarget", "HtmlCollection", "Node", diff --git a/packages/web/src/dom.rs b/packages/web/src/dom.rs index 6ca689d28..555e04e43 100644 --- a/packages/web/src/dom.rs +++ b/packages/web/src/dom.rs @@ -41,7 +41,7 @@ impl WebsysDom { Some(Ok(id)) => { break Ok(UserEvent { name: event_name_from_typ(&typ), - data: virtual_event_from_websys_event(event.clone()), + data: virtual_event_from_websys_event(event.clone(), target.clone()), element: Some(ElementId(id)), scope_id: None, priority: dioxus_core::EventPriority::Medium, @@ -57,7 +57,10 @@ impl WebsysDom { } else { break Ok(UserEvent { name: event_name_from_typ(&typ), - data: virtual_event_from_websys_event(event.clone()), + data: virtual_event_from_websys_event( + event.clone(), + target.clone(), + ), element: None, scope_id: None, priority: dioxus_core::EventPriority::Low, @@ -144,7 +147,10 @@ unsafe impl Sync for DioxusWebsysEvent {} // todo: some of these events are being casted to the wrong event type. // We need tests that simulate clicks/etc and make sure every event type works. -fn virtual_event_from_websys_event(event: web_sys::Event) -> Arc { +fn virtual_event_from_websys_event( + event: web_sys::Event, + target: Element, +) -> Arc { use dioxus_html::on::*; use dioxus_html::KeyCode; @@ -177,9 +183,6 @@ fn virtual_event_from_websys_event(event: web_sys::Event) -> Arc { - let evt: &web_sys::Event = event.dyn_ref().unwrap(); - - let target: web_sys::EventTarget = evt.target().unwrap(); let value: String = (&target) .dyn_ref() .map(|input: &web_sys::HtmlInputElement| { @@ -215,7 +218,38 @@ fn virtual_event_from_websys_event(event: web_sys::Event) -> Arc() { + let elements = form.elements(); + for x in 0..elements.length() { + let element = elements.item(x).unwrap(); + if let Some(name) = element.get_attribute("name") { + let value: String = (&element) + .dyn_ref() + .map(|input: &web_sys::HtmlInputElement| { + match input.type_().as_str() { + "checkbox" => { + match input.checked() { + true => "true".to_string(), + false => "false".to_string(), + } + }, + _ => input.value() + } + }) + .or_else(|| target.dyn_ref().map(|input: &web_sys::HtmlTextAreaElement| input.value())) + .or_else(|| target.dyn_ref().map(|input: &web_sys::HtmlSelectElement| input.value())) + .or_else(|| target.dyn_ref::().unwrap().text_content()) + .expect("only an InputElement or TextAreaElement or an element with contenteditable=true can have an oninput event listener"); + + values.insert(name, value); + } + } + } + + Arc::new(FormData { value, values }) } "click" | "contextmenu" | "doubleclick" | "drag" | "dragend" | "dragenter" | "dragexit" | "dragleave" | "dragover" | "dragstart" | "drop" | "mousedown" | "mouseenter" @@ -307,6 +341,7 @@ fn virtual_event_from_websys_event(event: web_sys::Event) -> Arc Arc::new(MediaData {}), "toggle" => Arc::new(ToggleData {}), + _ => Arc::new(()), } }