diff --git a/packages/html/Cargo.toml b/packages/html/Cargo.toml index 8d86a9672..d3efc839b 100644 --- a/packages/html/Cargo.toml +++ b/packages/html/Cargo.toml @@ -51,10 +51,7 @@ serde_json = "1" [features] default = ["serialize", "mounted", "eval"] serialize = [ - "serde", - "serde/rc", "serde_repr", - "serde_json", "euclid/serde", "keyboard-types/serde", "dioxus-core/serialize", diff --git a/packages/html/src/events/form.rs b/packages/html/src/events/form.rs index de8bb0dbf..ca1686cb7 100644 --- a/packages/html/src/events/form.rs +++ b/packages/html/src/events/form.rs @@ -4,6 +4,20 @@ use dioxus_core::Event; pub type FormEvent = Event; +/// A form value that may either be a list of values or a single value +#[cfg_attr( + feature = "serialize", + derive(serde::Serialize, serde::Deserialize), + // this will serialize Text(String) -> String and VecText(Vec) to Vec + serde(untagged) +)] +#[derive(Debug, Clone, PartialEq)] +pub enum FormValue { + Text(String), + VecText(Vec), +} + +/* DOMEvent: Send + SyncTarget relatedTarget */ pub struct FormData { inner: Box, } @@ -43,7 +57,7 @@ impl FormData { } /// Get the values of the form event - pub fn values(&self) -> HashMap> { + pub fn values(&self) -> HashMap { self.inner.values() } @@ -64,7 +78,7 @@ pub trait HasFormData: std::any::Any { Default::default() } - fn values(&self) -> HashMap> { + fn values(&self) -> HashMap { Default::default() } @@ -76,13 +90,37 @@ pub trait HasFormData: std::any::Any { fn as_any(&self) -> &dyn std::any::Any; } +impl FormData { + #[cfg(feature = "serialize")] + /// Parse the values into a struct with one field per value + pub fn parsed_values(&self) -> Result + where + T: serde::de::DeserializeOwned, + { + use serde::Serialize; + + fn convert_hashmap_to_json(hashmap: &HashMap) -> serde_json::Result + where + K: Serialize + std::hash::Hash + Eq, + V: Serialize, + { + serde_json::to_string(hashmap) + } + + let parsed_json = + convert_hashmap_to_json(&self.values()).expect("Failed to parse values to JSON"); + + serde_json::from_str(&parsed_json) + } +} + #[cfg(feature = "serialize")] /// A serialized form data object #[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Clone)] pub struct SerializedFormData { value: String, - values: HashMap>, - files: Option>, + values: HashMap, + files: Option, } #[cfg(feature = "serialize")] @@ -90,8 +128,8 @@ impl SerializedFormData { /// Create a new serialized form data object pub fn new( value: String, - values: HashMap>, - files: Option>, + values: HashMap, + files: Option, ) -> Self { Self { value, @@ -114,9 +152,9 @@ impl SerializedFormData { resolved_files.insert(file, bytes.unwrap_or_default()); } - Some(std::sync::Arc::new(SerializedFileEngine { + Some(SerializedFileEngine { files: resolved_files, - })) + }) } None => None, }, @@ -138,14 +176,14 @@ impl HasFormData for SerializedFormData { self.value.clone() } - fn values(&self) -> HashMap> { + fn values(&self) -> HashMap { self.values.clone() } fn files(&self) -> Option> { self.files .as_ref() - .map(|files| std::sync::Arc::clone(files) as std::sync::Arc) + .map(|files| std::sync::Arc::new(files.clone()) as _) } fn as_any(&self) -> &dyn std::any::Any { diff --git a/packages/interpreter/src/interpreter.js b/packages/interpreter/src/interpreter.js index 54f1d517c..10e07a385 100644 --- a/packages/interpreter/src/interpreter.js +++ b/packages/interpreter/src/interpreter.js @@ -109,8 +109,18 @@ function handler(event, name, bubbles, config) { const formData = new FormData(target); for (let name of formData.keys()) { - let value = formData.getAll(name); - contents.values[name] = value; + const fieldType = target.elements[name].type; + + switch (fieldType) { + case "select-multiple": + contents.values[name] = formData.getAll(name); + break; + + // add cases for fieldTypes that can hold multiple values here + default: + contents.values[name] = formData.get(name); + break; + } } } diff --git a/packages/rink/src/hooks.rs b/packages/rink/src/hooks.rs index 1ffa361f7..012c0c1eb 100644 --- a/packages/rink/src/hooks.rs +++ b/packages/rink/src/hooks.rs @@ -18,6 +18,7 @@ use dioxus_html::input_data::keyboard_types::{Code, Key, Location, Modifiers}; use dioxus_html::input_data::{ MouseButton as DioxusMouseButton, MouseButtonSet as DioxusMouseButtons, }; +use dioxus_html::FormValue; use dioxus_html::{event_bubbles, prelude::*}; use std::any::Any; use std::collections::HashMap; @@ -66,7 +67,7 @@ impl EventData { pub struct FormData { pub(crate) value: String, - pub(crate) values: HashMap>, + pub values: HashMap, pub(crate) files: Option, } @@ -76,7 +77,7 @@ impl HasFormData for FormData { self.value.clone() } - fn values(&self) -> HashMap> { + fn values(&self) -> HashMap { self.values.clone() } diff --git a/packages/web/src/dom.rs b/packages/web/src/dom.rs index da46e5012..9d08f09f4 100644 --- a/packages/web/src/dom.rs +++ b/packages/web/src/dom.rs @@ -9,9 +9,9 @@ use dioxus_core::{ BorrowedAttributeValue, ElementId, Mutation, Template, TemplateAttribute, TemplateNode, }; -use dioxus_html::{event_bubbles, MountedData, PlatformEventData}; -use dioxus_interpreter_js::get_node; -use dioxus_interpreter_js::{minimal_bindings, save_template, Channel}; +use dioxus_html::PlatformEventData; +use dioxus_html::{event_bubbles, MountedData}; +use dioxus_interpreter_js::{get_node, minimal_bindings, save_template, Channel}; use futures_channel::mpsc; use rustc_hash::FxHashMap; use wasm_bindgen::{closure::Closure, JsCast, JsValue}; diff --git a/packages/web/src/event.rs b/packages/web/src/event.rs index d1eed67a6..994bf304a 100644 --- a/packages/web/src/event.rs +++ b/packages/web/src/event.rs @@ -1,11 +1,11 @@ use std::{any::Any, collections::HashMap}; use dioxus_html::{ - FileEngine, FormData, HasFormData, HasImageData, HtmlEventConverter, ImageData, MountedData, - PlatformEventData, ScrollData, + prelude::FormValue, FileEngine, FormData, HasFormData, HasImageData, HtmlEventConverter, + ImageData, MountedData, PlatformEventData, ScrollData, }; use js_sys::Array; -use wasm_bindgen::{prelude::wasm_bindgen, JsCast}; +use wasm_bindgen::{prelude::wasm_bindgen, JsCast, JsValue}; use web_sys::{Document, Element, Event}; pub(crate) struct WebEventConverter; @@ -378,7 +378,7 @@ impl HasFormData for WebFormData { .expect("only an InputElement or TextAreaElement or an element with contenteditable=true can have an oninput event listener") } - fn values(&self) -> HashMap> { + fn values(&self) -> HashMap { let mut values = std::collections::HashMap::new(); // try to fill in form values @@ -388,10 +388,12 @@ impl HasFormData for WebFormData { if let Ok(array) = value.dyn_into::() { if let Some(name) = array.get(0).as_string() { if let Ok(item_values) = array.get(1).dyn_into::() { - let item_values = + let item_values: Vec = item_values.iter().filter_map(|v| v.as_string()).collect(); - values.insert(name, item_values); + values.insert(name, FormValue::VecText(item_values)); + } else if let Ok(item_value) = array.get(1).dyn_into::() { + values.insert(name, FormValue::Text(item_value.as_string().unwrap())); } } } @@ -432,7 +434,17 @@ impl HasFormData for WebFormData { const formData = new FormData(form); for (let name of formData.keys()) { - values.set(name, formData.getAll(name)); + const fieldType = target.elements[name].type; + + switch (fieldType) { + case "select-multiple": + contents.values[name] = formData.getAll(name); + break; + + // add cases for fieldTypes that can hold multiple values here + default: + contents.values[name] = formData.get(name); + break; } return values;