From 02e91d8b9aee3b1815c3ad86c506c0f2cc27741a Mon Sep 17 00:00:00 2001 From: Bunny Bites Date: Sat, 4 Nov 2023 21:50:11 +0530 Subject: [PATCH 01/19] add get_parsed_values function --- packages/rink/src/hooks.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/packages/rink/src/hooks.rs b/packages/rink/src/hooks.rs index f8c4500d2..a78194753 100644 --- a/packages/rink/src/hooks.rs +++ b/packages/rink/src/hooks.rs @@ -74,6 +74,29 @@ impl FormData { files: None, } } + + // ***** function to parse the 'values' to make it ready to use******* + // e.g - self.values = { username: ["rust"], password: ["dioxus"]} + // what we need it to be: { username: "rust", password: "dioxus"} + pub fn get_parsed_values(&self) -> Option> { + if (self.values.is_empty()) { + return None; + } + + let raw_values = self.values.clone(); + + let mut parsed_values: HashMap = HashMap::new(); + + for (fieldname, values) in raw_values.into_iter() { + // convert array of string value to string + // eg - ["rust", "javascript"] => "rust, javascript" + let prepared_value = values.join(", "); + + parsed_values.insert(fieldname, prepared_value); + } + + Some(parsed_values) + } } #[derive(Clone, Debug, PartialEq)] From d54ec57192b191cabb6f8ca138c369788fccaeb2 Mon Sep 17 00:00:00 2001 From: Bunny Bites Date: Sun, 5 Nov 2023 08:16:09 +0530 Subject: [PATCH 02/19] 1. migrated code to form.rs 2. made get_parsed_values as private fn. 3. handled multi-valued data while parsing --- packages/html/src/events/form.rs | 55 ++++++++++++++++++++++++++++++-- packages/rink/src/hooks.rs | 23 ------------- 2 files changed, 53 insertions(+), 25 deletions(-) diff --git a/packages/html/src/events/form.rs b/packages/html/src/events/form.rs index fab13482b..e3b35b098 100644 --- a/packages/html/src/events/form.rs +++ b/packages/html/src/events/form.rs @@ -1,9 +1,16 @@ use std::{any::Any, collections::HashMap, fmt::Debug}; use dioxus_core::Event; +use serde::{Deserialize, Serialize}; pub type FormEvent = Event; +#[derive(Serialize, Deserialize)] +enum ValueType { + Text(String), + VecText(Vec), +} + /* DOMEvent: Send + SyncTarget relatedTarget */ #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[derive(Clone)] @@ -23,6 +30,52 @@ pub struct FormData { pub files: Option>, } +impl FormData { + // ***** function to parse the 'values' to make it ready to use******* + // e.g - self.values = { username: ["rust"], password: ["dioxus"]} + // what we need it to be: { username: "rust", password: "dioxus"} + fn get_parsed_values(&self) -> Option> { + if self.values.is_empty() { + return None; + } + + let raw_values = self.values.clone(); + + let mut parsed_values: HashMap = HashMap::new(); + + for (fieldname, values) in raw_values.into_iter() { + /* + case 1 - multiple values, example { driving_types: ["manual", "automatic"] } + In this case we want to return the values as it is, NO point in making + driving_types: "manual, automatic" + + case 2 - single value, example { favourite_language: ["rust"] } + In this case we would want to deserialize the value as follows + favourite_language: "rust" + */ + if values.len() > 1 { + // handling multiple values - case 1 + parsed_values.insert(fieldname, ValueType::VecText(values.clone())); + } else { + // handle single value - case 2 + let prepared_value = values.into_iter().next()?; + parsed_values.insert(fieldname, ValueType::Text(prepared_value)); + } + } + + Some(parsed_values) + } + + pub fn parse_json(&self) -> Result + where + T: serde::de::DeserializeOwned, + { + let formatted = self.get_parsed_values(); + let as_string = serde_json::to_string(&formatted)?; + serde_json::from_str(&as_string) + } +} + #[cfg(feature = "serialize")] #[derive(serde::Serialize, serde::Deserialize)] struct SerializedFileEngine { @@ -60,8 +113,6 @@ fn deserialize_file_engine<'de, D>( where D: serde::Deserializer<'de>, { - use serde::Deserialize; - let Ok(file_engine) = SerializedFileEngine::deserialize(deserializer) else { return Ok(None); }; diff --git a/packages/rink/src/hooks.rs b/packages/rink/src/hooks.rs index a78194753..f8c4500d2 100644 --- a/packages/rink/src/hooks.rs +++ b/packages/rink/src/hooks.rs @@ -74,29 +74,6 @@ impl FormData { files: None, } } - - // ***** function to parse the 'values' to make it ready to use******* - // e.g - self.values = { username: ["rust"], password: ["dioxus"]} - // what we need it to be: { username: "rust", password: "dioxus"} - pub fn get_parsed_values(&self) -> Option> { - if (self.values.is_empty()) { - return None; - } - - let raw_values = self.values.clone(); - - let mut parsed_values: HashMap = HashMap::new(); - - for (fieldname, values) in raw_values.into_iter() { - // convert array of string value to string - // eg - ["rust", "javascript"] => "rust, javascript" - let prepared_value = values.join(", "); - - parsed_values.insert(fieldname, prepared_value); - } - - Some(parsed_values) - } } #[derive(Clone, Debug, PartialEq)] From f1533781a4e0caed102f8e74ae836f37239bbb22 Mon Sep 17 00:00:00 2001 From: Bunny Bites Date: Thu, 9 Nov 2023 00:38:22 +0530 Subject: [PATCH 03/19] add fn to convert hashmap to json --- packages/html/src/events/form.rs | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/packages/html/src/events/form.rs b/packages/html/src/events/form.rs index e3b35b098..6346a99e6 100644 --- a/packages/html/src/events/form.rs +++ b/packages/html/src/events/form.rs @@ -1,11 +1,12 @@ use std::{any::Any, collections::HashMap, fmt::Debug}; use dioxus_core::Event; -use serde::{Deserialize, Serialize}; +use serde::{de::Error, Deserialize, Serialize}; pub type FormEvent = Event; -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Debug)] +#[serde(untagged)] // this will serialize Text(String) -> String and VecText(Vec) to Vec enum ValueType { Text(String), VecText(Vec), @@ -30,13 +31,21 @@ pub struct FormData { pub files: Option>, } +fn convert_hashmap_to_json(hashmap: &HashMap) -> serde_json::Result +where + K: Serialize + std::hash::Hash + Eq, + V: Serialize, +{ + serde_json::to_string(hashmap) +} + impl FormData { // ***** function to parse the 'values' to make it ready to use******* // e.g - self.values = { username: ["rust"], password: ["dioxus"]} // what we need it to be: { username: "rust", password: "dioxus"} - fn get_parsed_values(&self) -> Option> { + fn get_parsed_values(&self) -> Result { if self.values.is_empty() { - return None; + return Err(serde_json::Error::custom("Values is empty")); } let raw_values = self.values.clone(); @@ -58,21 +67,24 @@ impl FormData { parsed_values.insert(fieldname, ValueType::VecText(values.clone())); } else { // handle single value - case 2 - let prepared_value = values.into_iter().next()?; + let prepared_value = values.into_iter().next().unwrap(); parsed_values.insert(fieldname, ValueType::Text(prepared_value)); } } - Some(parsed_values) + // convert HashMap to JSON string + convert_hashmap_to_json(&parsed_values) } pub fn parse_json(&self) -> Result where T: serde::de::DeserializeOwned, { - let formatted = self.get_parsed_values(); - let as_string = serde_json::to_string(&formatted)?; - serde_json::from_str(&as_string) + let parsed_json = self + .get_parsed_values() + .expect("Failed to parse values to JSON"); + + serde_json::from_str(&parsed_json) } } From 1a9d1eda19827c53da48a966d7418ad1f241466b Mon Sep 17 00:00:00 2001 From: Bunny Bites Date: Thu, 9 Nov 2023 00:41:50 +0530 Subject: [PATCH 04/19] optimize hashmap insertion --- packages/html/src/events/form.rs | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/packages/html/src/events/form.rs b/packages/html/src/events/form.rs index 6346a99e6..1303dc209 100644 --- a/packages/html/src/events/form.rs +++ b/packages/html/src/events/form.rs @@ -62,14 +62,21 @@ impl FormData { In this case we would want to deserialize the value as follows favourite_language: "rust" */ - if values.len() > 1 { - // handling multiple values - case 1 - parsed_values.insert(fieldname, ValueType::VecText(values.clone())); - } else { - // handle single value - case 2 - let prepared_value = values.into_iter().next().unwrap(); - parsed_values.insert(fieldname, ValueType::Text(prepared_value)); - } + parsed_values.insert( + fieldname, + if values.len() > 1 { + // handling multiple values - case 1 + ValueType::VecText(values) + } else { + // handle single value - case 2 + ValueType::Text( + values + .into_iter() + .next() + .ok_or_else(|| serde_json::Error::custom("Values array is empty"))?, + ) + }, + ); } // convert HashMap to JSON string From 2cbbdc23c6bda33609cd10d85e0d3cc6450b7186 Mon Sep 17 00:00:00 2001 From: Bunny Bites Date: Thu, 9 Nov 2023 11:04:47 +0530 Subject: [PATCH 05/19] add impl fn for FormData to get input types --- packages/html/src/events/form.rs | 15 ++++++++++++++- packages/rink/src/hooks.rs | 3 +++ packages/rink/src/widgets/button.rs | 1 + packages/rink/src/widgets/checkbox.rs | 1 + packages/rink/src/widgets/slider.rs | 1 + packages/rink/src/widgets/text_like.rs | 1 + packages/web/src/dom.rs | 18 +++++++++++++++++- 7 files changed, 38 insertions(+), 2 deletions(-) diff --git a/packages/html/src/events/form.rs b/packages/html/src/events/form.rs index 1303dc209..2e2fc5934 100644 --- a/packages/html/src/events/form.rs +++ b/packages/html/src/events/form.rs @@ -20,6 +20,8 @@ pub struct FormData { pub values: HashMap>, + pub value_types: HashMap, + #[cfg_attr( feature = "serialize", serde( @@ -53,6 +55,17 @@ impl FormData { let mut parsed_values: HashMap = HashMap::new(); for (fieldname, values) in raw_values.into_iter() { + // check if the fieldname can hold multiple values based on its types + let field_type = self + .value_types + .get(&fieldname) + .expect("Provided invalid field"); + + let is_multi_valued_input = match field_type.as_str() { + "select" | "checkbox" => true, + _ => false, + }; + /* case 1 - multiple values, example { driving_types: ["manual", "automatic"] } In this case we want to return the values as it is, NO point in making @@ -64,7 +77,7 @@ impl FormData { */ parsed_values.insert( fieldname, - if values.len() > 1 { + if is_multi_valued_input { // handling multiple values - case 1 ValueType::VecText(values) } else { diff --git a/packages/rink/src/hooks.rs b/packages/rink/src/hooks.rs index f8c4500d2..47efc9e7e 100644 --- a/packages/rink/src/hooks.rs +++ b/packages/rink/src/hooks.rs @@ -63,6 +63,8 @@ pub struct FormData { pub values: HashMap>, + pub value_types: HashMap, + pub files: Option, } @@ -71,6 +73,7 @@ impl FormData { dioxus_html::FormData { value: self.value, values: self.values, + value_types: self.value_types, files: None, } } diff --git a/packages/rink/src/widgets/button.rs b/packages/rink/src/widgets/button.rs index 523b2392a..41be40da5 100644 --- a/packages/rink/src/widgets/button.rs +++ b/packages/rink/src/widgets/button.rs @@ -93,6 +93,7 @@ impl Button { let data = FormData { value: self.value.to_string(), values: HashMap::new(), + value_types: HashMap::new(), files: None, }; ctx.send(crate::Event { diff --git a/packages/rink/src/widgets/checkbox.rs b/packages/rink/src/widgets/checkbox.rs index fab809ed2..ea5a0ba53 100644 --- a/packages/rink/src/widgets/checkbox.rs +++ b/packages/rink/src/widgets/checkbox.rs @@ -129,6 +129,7 @@ impl CheckBox { .then(|| self.value.to_string()) .unwrap_or_default(), values: HashMap::new(), + value_types: HashMap::new(), files: None, }; { diff --git a/packages/rink/src/widgets/slider.rs b/packages/rink/src/widgets/slider.rs index 7080b6c12..dd1b50a7b 100644 --- a/packages/rink/src/widgets/slider.rs +++ b/packages/rink/src/widgets/slider.rs @@ -198,6 +198,7 @@ impl Slider { let data = FormData { value: self.value.to_string(), values: HashMap::new(), + value_types: HashMap::new(), files: None, }; ctx.send(Event { diff --git a/packages/rink/src/widgets/text_like.rs b/packages/rink/src/widgets/text_like.rs index 6521073d0..c9dacb74e 100644 --- a/packages/rink/src/widgets/text_like.rs +++ b/packages/rink/src/widgets/text_like.rs @@ -171,6 +171,7 @@ impl TextLike { let data: FormData = FormData { value: self.text.clone(), values: HashMap::new(), + value_types: HashMap::new(), files: None, }; let ctx: UniqueView = world.borrow().expect("expected widget context"); diff --git a/packages/web/src/dom.rs b/packages/web/src/dom.rs index f45d6558a..556d9ab9a 100644 --- a/packages/web/src/dom.rs +++ b/packages/web/src/dom.rs @@ -15,7 +15,7 @@ use dioxus_interpreter_js::{get_node, minimal_bindings, save_template, Channel}; use futures_channel::mpsc; use js_sys::Array; use rustc_hash::FxHashMap; -use std::{any::Any, rc::Rc}; +use std::{any::Any, collections::HashMap, rc::Rc}; use wasm_bindgen::{closure::Closure, prelude::wasm_bindgen, JsCast, JsValue}; use web_sys::{Document, Element, Event}; @@ -365,6 +365,21 @@ fn read_input_to_data(target: Element) -> Rc { }) .expect("only an InputElement or TextAreaElement or an element with contenteditable=true can have an oninput event listener"); + let mut value_types = HashMap::new(); + + // to get the input_type for the corresponding input + for input_el in target.dyn_ref::().into_iter() { + for index in 0..input_el.length() { + if let Some(element) = input_el.get_with_index(index as u32) { + if let Some(input) = element.dyn_into::().ok() { + let name = input.name(); + let input_type = input.type_(); + value_types.insert(name, input_type); + } + } + } + } + let mut values = std::collections::HashMap::new(); // try to fill in form values @@ -400,6 +415,7 @@ fn read_input_to_data(target: Element) -> Rc { Rc::new(FormData { value, values, + value_types, files, }) } From 9f0e00029503ce0a3807ea37f2aa72c44da9e5af Mon Sep 17 00:00:00 2001 From: Bunny Bites Date: Thu, 9 Nov 2023 11:09:07 +0530 Subject: [PATCH 06/19] provide proper error message --- packages/html/src/events/form.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/html/src/events/form.rs b/packages/html/src/events/form.rs index 2e2fc5934..e86f72167 100644 --- a/packages/html/src/events/form.rs +++ b/packages/html/src/events/form.rs @@ -47,7 +47,7 @@ impl FormData { // what we need it to be: { username: "rust", password: "dioxus"} fn get_parsed_values(&self) -> Result { if self.values.is_empty() { - return Err(serde_json::Error::custom("Values is empty")); + return Err(serde_json::Error::custom("Values array is empty")); } let raw_values = self.values.clone(); From c6b39c05b704e7f0151eff00c5f6f774d7883702 Mon Sep 17 00:00:00 2001 From: Bunny Bites Date: Fri, 10 Nov 2023 11:44:19 +0530 Subject: [PATCH 07/19] 1. remove value_types from FormData. 2. modify type of values to accept dynamic type. --- packages/html/src/events/form.rs | 69 +++----------------------- packages/rink/src/hooks.rs | 5 +- packages/rink/src/widgets/button.rs | 1 - packages/rink/src/widgets/slider.rs | 1 - packages/rink/src/widgets/text_like.rs | 1 - packages/web/src/dom.rs | 40 +++++++-------- 6 files changed, 26 insertions(+), 91 deletions(-) diff --git a/packages/html/src/events/form.rs b/packages/html/src/events/form.rs index e86f72167..5bdf5124d 100644 --- a/packages/html/src/events/form.rs +++ b/packages/html/src/events/form.rs @@ -1,13 +1,13 @@ use std::{any::Any, collections::HashMap, fmt::Debug}; use dioxus_core::Event; -use serde::{de::Error, Deserialize, Serialize}; +use serde::{Deserialize, Serialize}; pub type FormEvent = Event; -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] #[serde(untagged)] // this will serialize Text(String) -> String and VecText(Vec) to Vec -enum ValueType { +pub enum ValueType { Text(String), VecText(Vec), } @@ -18,9 +18,7 @@ enum ValueType { pub struct FormData { pub value: String, - pub values: HashMap>, - - pub value_types: HashMap, + pub values: HashMap, #[cfg_attr( feature = "serialize", @@ -42,67 +40,12 @@ where } impl FormData { - // ***** function to parse the 'values' to make it ready to use******* - // e.g - self.values = { username: ["rust"], password: ["dioxus"]} - // what we need it to be: { username: "rust", password: "dioxus"} - fn get_parsed_values(&self) -> Result { - if self.values.is_empty() { - return Err(serde_json::Error::custom("Values array is empty")); - } - - let raw_values = self.values.clone(); - - let mut parsed_values: HashMap = HashMap::new(); - - for (fieldname, values) in raw_values.into_iter() { - // check if the fieldname can hold multiple values based on its types - let field_type = self - .value_types - .get(&fieldname) - .expect("Provided invalid field"); - - let is_multi_valued_input = match field_type.as_str() { - "select" | "checkbox" => true, - _ => false, - }; - - /* - case 1 - multiple values, example { driving_types: ["manual", "automatic"] } - In this case we want to return the values as it is, NO point in making - driving_types: "manual, automatic" - - case 2 - single value, example { favourite_language: ["rust"] } - In this case we would want to deserialize the value as follows - favourite_language: "rust" - */ - parsed_values.insert( - fieldname, - if is_multi_valued_input { - // handling multiple values - case 1 - ValueType::VecText(values) - } else { - // handle single value - case 2 - ValueType::Text( - values - .into_iter() - .next() - .ok_or_else(|| serde_json::Error::custom("Values array is empty"))?, - ) - }, - ); - } - - // convert HashMap to JSON string - convert_hashmap_to_json(&parsed_values) - } - pub fn parse_json(&self) -> Result where T: serde::de::DeserializeOwned, { - let parsed_json = self - .get_parsed_values() - .expect("Failed to parse values to JSON"); + let parsed_json = + convert_hashmap_to_json(&self.values.clone()).expect("Failed to parse values to JSON"); serde_json::from_str(&parsed_json) } diff --git a/packages/rink/src/hooks.rs b/packages/rink/src/hooks.rs index 47efc9e7e..f457de29f 100644 --- a/packages/rink/src/hooks.rs +++ b/packages/rink/src/hooks.rs @@ -61,9 +61,7 @@ impl EventData { pub struct FormData { pub value: String, - pub values: HashMap>, - - pub value_types: HashMap, + pub values: HashMap, pub files: Option, } @@ -73,7 +71,6 @@ impl FormData { dioxus_html::FormData { value: self.value, values: self.values, - value_types: self.value_types, files: None, } } diff --git a/packages/rink/src/widgets/button.rs b/packages/rink/src/widgets/button.rs index 41be40da5..523b2392a 100644 --- a/packages/rink/src/widgets/button.rs +++ b/packages/rink/src/widgets/button.rs @@ -93,7 +93,6 @@ impl Button { let data = FormData { value: self.value.to_string(), values: HashMap::new(), - value_types: HashMap::new(), files: None, }; ctx.send(crate::Event { diff --git a/packages/rink/src/widgets/slider.rs b/packages/rink/src/widgets/slider.rs index dd1b50a7b..7080b6c12 100644 --- a/packages/rink/src/widgets/slider.rs +++ b/packages/rink/src/widgets/slider.rs @@ -198,7 +198,6 @@ impl Slider { let data = FormData { value: self.value.to_string(), values: HashMap::new(), - value_types: HashMap::new(), files: None, }; ctx.send(Event { diff --git a/packages/rink/src/widgets/text_like.rs b/packages/rink/src/widgets/text_like.rs index c9dacb74e..6521073d0 100644 --- a/packages/rink/src/widgets/text_like.rs +++ b/packages/rink/src/widgets/text_like.rs @@ -171,7 +171,6 @@ impl TextLike { let data: FormData = FormData { value: self.text.clone(), values: HashMap::new(), - value_types: HashMap::new(), files: None, }; let ctx: UniqueView = world.borrow().expect("expected widget context"); diff --git a/packages/web/src/dom.rs b/packages/web/src/dom.rs index 556d9ab9a..3188e0935 100644 --- a/packages/web/src/dom.rs +++ b/packages/web/src/dom.rs @@ -10,7 +10,7 @@ use dioxus_core::{ BorrowedAttributeValue, ElementId, Mutation, Template, TemplateAttribute, TemplateNode, }; -use dioxus_html::{event_bubbles, CompositionData, FormData, MountedData}; +use dioxus_html::{event_bubbles, CompositionData, FormData, MountedData, ValueType}; use dioxus_interpreter_js::{get_node, minimal_bindings, save_template, Channel}; use futures_channel::mpsc; use js_sys::Array; @@ -365,22 +365,7 @@ fn read_input_to_data(target: Element) -> Rc { }) .expect("only an InputElement or TextAreaElement or an element with contenteditable=true can have an oninput event listener"); - let mut value_types = HashMap::new(); - - // to get the input_type for the corresponding input - for input_el in target.dyn_ref::().into_iter() { - for index in 0..input_el.length() { - if let Some(element) = input_el.get_with_index(index as u32) { - if let Some(input) = element.dyn_into::().ok() { - let name = input.name(); - let input_type = input.type_(); - value_types.insert(name, input_type); - } - } - } - } - - let mut values = std::collections::HashMap::new(); + let mut values = HashMap::new(); // try to fill in form values if let Some(form) = target.dyn_ref::() { @@ -389,10 +374,12 @@ fn read_input_to_data(target: Element) -> Rc { 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, ValueType::VecText(item_values)); + } else if let Ok(item_value) = array.get(1).dyn_into::() { + values.insert(name, ValueType::Text(item_value.as_string().unwrap())); } } } @@ -415,7 +402,6 @@ fn read_input_to_data(target: Element) -> Rc { Rc::new(FormData { value, values, - value_types, files, }) } @@ -424,10 +410,22 @@ fn read_input_to_data(target: Element) -> Rc { #[wasm_bindgen(inline_js = r#" export function get_form_data(form) { let values = new Map(); + const formData = new FormData(form); for (let name of formData.keys()) { - values.set(name, formData.getAll(name)); + const fieldType = form.elements[name].type; + + switch (fieldType) { + case "select-multiple": + values.set(name, formData.getAll(name)); + break; + + // add cases for fieldTypes that can hold multiple values here + default: + values.set(name, formData.get(name)); + break; + } } return values; From 6398af1d65fe10a0f4aefc831cdbe78947bf1de8 Mon Sep 17 00:00:00 2001 From: Bunny Bites Date: Fri, 10 Nov 2023 23:23:26 +0530 Subject: [PATCH 08/19] code cleanup --- packages/rink/src/hooks.rs | 1 + packages/rink/src/widgets/checkbox.rs | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/rink/src/hooks.rs b/packages/rink/src/hooks.rs index f457de29f..494d3224f 100644 --- a/packages/rink/src/hooks.rs +++ b/packages/rink/src/hooks.rs @@ -13,6 +13,7 @@ use dioxus_html::geometry::{ use dioxus_html::input_data::keyboard_types::{Code, Key, Location, Modifiers}; use dioxus_html::input_data::MouseButtonSet as DioxusMouseButtons; use dioxus_html::input_data::{MouseButton as DioxusMouseButton, MouseButtonSet}; +use dioxus_html::ValueType; use dioxus_html::{event_bubbles, FocusData, KeyboardData, MouseData, WheelData}; use std::any::Any; use std::collections::HashMap; diff --git a/packages/rink/src/widgets/checkbox.rs b/packages/rink/src/widgets/checkbox.rs index ea5a0ba53..fab809ed2 100644 --- a/packages/rink/src/widgets/checkbox.rs +++ b/packages/rink/src/widgets/checkbox.rs @@ -129,7 +129,6 @@ impl CheckBox { .then(|| self.value.to_string()) .unwrap_or_default(), values: HashMap::new(), - value_types: HashMap::new(), files: None, }; { From 2b459e6fc14a75aa3820905d9f5e35f6d69d8504 Mon Sep 17 00:00:00 2001 From: Bunny Bites Date: Fri, 10 Nov 2023 23:32:41 +0530 Subject: [PATCH 09/19] 1. optimize import. 2. modify post event serialization. --- packages/interpreter/src/interpreter.js | 14 ++++++++++++-- packages/rink/src/hooks.rs | 3 +-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/packages/interpreter/src/interpreter.js b/packages/interpreter/src/interpreter.js index a03983ffa..2a1841171 100644 --- a/packages/interpreter/src/interpreter.js +++ b/packages/interpreter/src/interpreter.js @@ -453,8 +453,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 = form.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 494d3224f..8842ffe60 100644 --- a/packages/rink/src/hooks.rs +++ b/packages/rink/src/hooks.rs @@ -13,8 +13,7 @@ use dioxus_html::geometry::{ use dioxus_html::input_data::keyboard_types::{Code, Key, Location, Modifiers}; use dioxus_html::input_data::MouseButtonSet as DioxusMouseButtons; use dioxus_html::input_data::{MouseButton as DioxusMouseButton, MouseButtonSet}; -use dioxus_html::ValueType; -use dioxus_html::{event_bubbles, FocusData, KeyboardData, MouseData, WheelData}; +use dioxus_html::{event_bubbles, FocusData, KeyboardData, MouseData, ValueType, WheelData}; use std::any::Any; use std::collections::HashMap; use std::{ From 8183935f661cd489b3fa7e101c2ed147547912b3 Mon Sep 17 00:00:00 2001 From: Bunny Bites Date: Sat, 11 Nov 2023 09:36:26 +0530 Subject: [PATCH 10/19] remove unwanted cloning while passing reference. --- packages/html/src/events/form.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/html/src/events/form.rs b/packages/html/src/events/form.rs index 5bdf5124d..de2494a50 100644 --- a/packages/html/src/events/form.rs +++ b/packages/html/src/events/form.rs @@ -45,7 +45,7 @@ impl FormData { T: serde::de::DeserializeOwned, { let parsed_json = - convert_hashmap_to_json(&self.values.clone()).expect("Failed to parse values to JSON"); + convert_hashmap_to_json(&self.values).expect("Failed to parse values to JSON"); serde_json::from_str(&parsed_json) } From 48295b021c377ce53921d50c4521ba183b2de6d6 Mon Sep 17 00:00:00 2001 From: Bunny Bites Date: Sat, 11 Nov 2023 21:27:44 +0530 Subject: [PATCH 11/19] remove optional config of serde_json --- packages/html/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/html/Cargo.toml b/packages/html/Cargo.toml index 5644b440c..b8e91e5ac 100644 --- a/packages/html/Cargo.toml +++ b/packages/html/Cargo.toml @@ -23,7 +23,7 @@ serde-value = "0.7.0" tokio = { workspace = true, features = ["fs", "io-util"], optional = true } rfd = { version = "0.11.3", optional = true } async-channel = "1.8.0" -serde_json = { version = "1", optional = true } +serde_json = { version = "1" } [dependencies.web-sys] optional = true From 13a82a370500ad0c7f498618d20bd5a3b6f133f6 Mon Sep 17 00:00:00 2001 From: Bunny Bites Date: Sat, 11 Nov 2023 21:55:08 +0530 Subject: [PATCH 12/19] remove serde and serde_json from serialize dependency. --- packages/html/Cargo.toml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/html/Cargo.toml b/packages/html/Cargo.toml index b8e91e5ac..0d9085f6a 100644 --- a/packages/html/Cargo.toml +++ b/packages/html/Cargo.toml @@ -12,7 +12,7 @@ keywords = ["dom", "ui", "gui", "react"] [dependencies] dioxus-core = { workspace = true } dioxus-rsx = { workspace = true, features = ["hot_reload"], optional = true } -serde = { version = "1", features = ["derive"], optional = true } +serde = { version = "1", features = ["derive"] } serde_repr = { version = "0.1", optional = true } wasm-bindgen = { workspace = true, optional = true } euclid = "0.22.7" @@ -50,9 +50,7 @@ serde_json = "1" [features] default = ["serialize", "mounted"] serialize = [ - "serde", "serde_repr", - "serde_json", "euclid/serde", "keyboard-types/serde", "dioxus-core/serialize", From a74e6d82c73338cf9f5d05ecd47d1f19e86d0a14 Mon Sep 17 00:00:00 2001 From: Bunny Bites Date: Sun, 12 Nov 2023 13:09:51 +0530 Subject: [PATCH 13/19] rename parse_json to parsed_values --- packages/html/src/events/form.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/html/src/events/form.rs b/packages/html/src/events/form.rs index de2494a50..d48c20370 100644 --- a/packages/html/src/events/form.rs +++ b/packages/html/src/events/form.rs @@ -40,7 +40,7 @@ where } impl FormData { - pub fn parse_json(&self) -> Result + pub fn parsed_values(&self) -> Result where T: serde::de::DeserializeOwned, { From 67670875bba323829dc9f9097700b07dffc66ee8 Mon Sep 17 00:00:00 2001 From: Bunny Bites Date: Tue, 14 Nov 2023 01:13:47 +0530 Subject: [PATCH 14/19] fix parsing issue for desktop platform --- packages/interpreter/src/interpreter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/interpreter/src/interpreter.js b/packages/interpreter/src/interpreter.js index 2a1841171..7f55c2da3 100644 --- a/packages/interpreter/src/interpreter.js +++ b/packages/interpreter/src/interpreter.js @@ -453,7 +453,7 @@ function handler(event, name, bubbles, config) { const formData = new FormData(target); for (let name of formData.keys()) { - const fieldType = form.elements[name].type; + const fieldType = target.elements[name].type; switch (fieldType) { case "select-multiple": From 20bbf959797f4d6ebdc2e76e2b61ddba2cbfd147 Mon Sep 17 00:00:00 2001 From: ealmloff Date: Tue, 14 Nov 2023 10:45:53 -0600 Subject: [PATCH 15/19] document parsed_values and FormValue --- packages/html/src/events/form.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/html/src/events/form.rs b/packages/html/src/events/form.rs index d48c20370..1f8127f3b 100644 --- a/packages/html/src/events/form.rs +++ b/packages/html/src/events/form.rs @@ -5,9 +5,10 @@ use serde::{Deserialize, Serialize}; pub type FormEvent = Event; +/// A form value that may either be a list of values or a single value #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] #[serde(untagged)] // this will serialize Text(String) -> String and VecText(Vec) to Vec -pub enum ValueType { +pub enum FormValue { Text(String), VecText(Vec), } @@ -18,7 +19,7 @@ pub enum ValueType { pub struct FormData { pub value: String, - pub values: HashMap, + pub values: HashMap, #[cfg_attr( feature = "serialize", @@ -40,6 +41,7 @@ where } impl FormData { + /// Parse the values into a struct with one field per value pub fn parsed_values(&self) -> Result where T: serde::de::DeserializeOwned, From 4631050a043da826f5e35f841cf326c63a1f9be1 Mon Sep 17 00:00:00 2001 From: ealmloff Date: Tue, 14 Nov 2023 10:52:39 -0600 Subject: [PATCH 16/19] fix FormValue --- packages/rink/src/hooks.rs | 4 ++-- packages/web/src/dom.rs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/rink/src/hooks.rs b/packages/rink/src/hooks.rs index 8842ffe60..109b39dd7 100644 --- a/packages/rink/src/hooks.rs +++ b/packages/rink/src/hooks.rs @@ -13,7 +13,7 @@ use dioxus_html::geometry::{ use dioxus_html::input_data::keyboard_types::{Code, Key, Location, Modifiers}; use dioxus_html::input_data::MouseButtonSet as DioxusMouseButtons; use dioxus_html::input_data::{MouseButton as DioxusMouseButton, MouseButtonSet}; -use dioxus_html::{event_bubbles, FocusData, KeyboardData, MouseData, ValueType, WheelData}; +use dioxus_html::{event_bubbles, FocusData, KeyboardData, MouseData, FormValue, WheelData}; use std::any::Any; use std::collections::HashMap; use std::{ @@ -61,7 +61,7 @@ impl EventData { pub struct FormData { pub value: String, - pub values: HashMap, + pub values: HashMap, pub files: Option, } diff --git a/packages/web/src/dom.rs b/packages/web/src/dom.rs index 3188e0935..1f3cecb0f 100644 --- a/packages/web/src/dom.rs +++ b/packages/web/src/dom.rs @@ -10,7 +10,7 @@ use dioxus_core::{ BorrowedAttributeValue, ElementId, Mutation, Template, TemplateAttribute, TemplateNode, }; -use dioxus_html::{event_bubbles, CompositionData, FormData, MountedData, ValueType}; +use dioxus_html::{event_bubbles, CompositionData, FormData, MountedData, FormValue}; use dioxus_interpreter_js::{get_node, minimal_bindings, save_template, Channel}; use futures_channel::mpsc; use js_sys::Array; @@ -377,9 +377,9 @@ fn read_input_to_data(target: Element) -> Rc { let item_values: Vec = item_values.iter().filter_map(|v| v.as_string()).collect(); - values.insert(name, ValueType::VecText(item_values)); + values.insert(name, FormValue::VecText(item_values)); } else if let Ok(item_value) = array.get(1).dyn_into::() { - values.insert(name, ValueType::Text(item_value.as_string().unwrap())); + values.insert(name, FormValue::Text(item_value.as_string().unwrap())); } } } From cf77325f678f1a598b5a2642cd71535f7b3d487a Mon Sep 17 00:00:00 2001 From: Bunny Bites Date: Tue, 14 Nov 2023 23:27:50 +0530 Subject: [PATCH 17/19] fix code format issues (pipeline) --- packages/rink/src/hooks.rs | 2 +- packages/web/src/dom.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/rink/src/hooks.rs b/packages/rink/src/hooks.rs index 109b39dd7..96d6c9f62 100644 --- a/packages/rink/src/hooks.rs +++ b/packages/rink/src/hooks.rs @@ -13,7 +13,7 @@ use dioxus_html::geometry::{ use dioxus_html::input_data::keyboard_types::{Code, Key, Location, Modifiers}; use dioxus_html::input_data::MouseButtonSet as DioxusMouseButtons; use dioxus_html::input_data::{MouseButton as DioxusMouseButton, MouseButtonSet}; -use dioxus_html::{event_bubbles, FocusData, KeyboardData, MouseData, FormValue, WheelData}; +use dioxus_html::{event_bubbles, FocusData, FormValue, KeyboardData, MouseData, WheelData}; use std::any::Any; use std::collections::HashMap; use std::{ diff --git a/packages/web/src/dom.rs b/packages/web/src/dom.rs index 1f3cecb0f..d9fec9203 100644 --- a/packages/web/src/dom.rs +++ b/packages/web/src/dom.rs @@ -10,7 +10,7 @@ use dioxus_core::{ BorrowedAttributeValue, ElementId, Mutation, Template, TemplateAttribute, TemplateNode, }; -use dioxus_html::{event_bubbles, CompositionData, FormData, MountedData, FormValue}; +use dioxus_html::{event_bubbles, CompositionData, FormData, FormValue, MountedData}; use dioxus_interpreter_js::{get_node, minimal_bindings, save_template, Channel}; use futures_channel::mpsc; use js_sys::Array; From 44c969702110a2cdc9e11bc0a711674b16fcd4f5 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 8 Jan 2024 10:26:33 -0800 Subject: [PATCH 18/19] Fix merge conflicts --- packages/html/Cargo.toml | 2 +- packages/html/src/events/form.rs | 64 +++-------- packages/rink/src/hooks.rs | 4 +- packages/web/src/dom.rs | 187 +------------------------------ packages/web/src/event.rs | 26 +++-- 5 files changed, 37 insertions(+), 246 deletions(-) diff --git a/packages/html/Cargo.toml b/packages/html/Cargo.toml index 1c5561cc7..d3efc839b 100644 --- a/packages/html/Cargo.toml +++ b/packages/html/Cargo.toml @@ -24,7 +24,7 @@ serde-value = "0.7.0" tokio = { workspace = true, features = ["fs", "io-util"], optional = true } rfd = { version = "0.12", optional = true } async-channel = "1.8.0" -serde_json = { version = "1" } +serde_json = { version = "1", optional = true } [dependencies.web-sys] optional = true diff --git a/packages/html/src/events/form.rs b/packages/html/src/events/form.rs index bc052bbe2..ee69104b9 100644 --- a/packages/html/src/events/form.rs +++ b/packages/html/src/events/form.rs @@ -1,4 +1,4 @@ -use std::{any::Any, collections::HashMap, fmt::Debug}; +use std::{any::Any, collections::HashMap, fmt::Debug, sync::Arc}; use dioxus_core::Event; use serde::{Deserialize, Serialize}; @@ -14,20 +14,13 @@ pub enum FormValue { } /* DOMEvent: Send + SyncTarget relatedTarget */ -#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] -#[derive(Clone)] pub struct FormData { inner: Box, - pub values: HashMap, } impl From for FormData { fn from(e: E) -> Self { - // todo: fix this? - Self { - inner: Box::new(e), - values: Default::default(), - } + Self { inner: Box::new(e) } } } @@ -51,7 +44,6 @@ impl FormData { pub fn new(event: impl HasFormData + 'static) -> Self { Self { inner: Box::new(event), - values: Default::default(), } } @@ -61,7 +53,7 @@ impl FormData { } /// Get the values of the form event - pub fn values(&self) -> HashMap> { + pub fn values(&self) -> HashMap { self.inner.values() } @@ -82,7 +74,7 @@ pub trait HasFormData: std::any::Any { Default::default() } - fn values(&self) -> HashMap> { + fn values(&self) -> HashMap { Default::default() } @@ -109,7 +101,7 @@ impl FormData { T: serde::de::DeserializeOwned, { let parsed_json = - convert_hashmap_to_json(&self.values).expect("Failed to parse values to JSON"); + convert_hashmap_to_json(&self.values()).expect("Failed to parse values to JSON"); serde_json::from_str(&parsed_json) } @@ -120,8 +112,8 @@ impl FormData { #[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Clone)] pub struct SerializedFormData { value: String, - values: HashMap>, - files: Option>, + values: HashMap, + files: Option, } #[cfg(feature = "serialize")] @@ -129,8 +121,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, @@ -153,9 +145,9 @@ impl SerializedFormData { resolved_files.insert(file, bytes.unwrap_or_default()); } - Some(std::sync::Arc::new(SerializedFileEngine { + Some(SerializedFileEngine { files: resolved_files, - })) + }) } None => None, }, @@ -177,14 +169,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| Arc::new(files.clone()) as _) } fn as_any(&self) -> &dyn std::any::Any { @@ -240,36 +232,6 @@ impl FileEngine for SerializedFileEngine { } } -#[cfg(feature = "serialize")] -fn deserialize_file_engine<'de, D>( - deserializer: D, -) -> Result>, D::Error> -where - D: serde::Deserializer<'de>, -{ - let Ok(file_engine) = SerializedFileEngine::deserialize(deserializer) else { - return Ok(None); - }; - - let file_engine = std::sync::Arc::new(file_engine); - Ok(Some(file_engine)) -} - -impl PartialEq for FormData { - fn eq(&self, other: &Self) -> bool { - self.value == other.value && self.values == other.values - } -} - -impl Debug for FormData { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("FormEvent") - .field("value", &self.value) - .field("values", &self.values) - .finish() - } -} - #[async_trait::async_trait(?Send)] pub trait FileEngine { // get a list of file names diff --git a/packages/rink/src/hooks.rs b/packages/rink/src/hooks.rs index 2b094c768..012c0c1eb 100644 --- a/packages/rink/src/hooks.rs +++ b/packages/rink/src/hooks.rs @@ -18,8 +18,8 @@ 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 dioxus_html::{FocusData, FormValue, KeyboardData, MouseData, WheelData}; use std::any::Any; use std::collections::HashMap; use std::{ @@ -77,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 83b4173e6..9d08f09f4 100644 --- a/packages/web/src/dom.rs +++ b/packages/web/src/dom.rs @@ -9,15 +9,11 @@ use dioxus_core::{ BorrowedAttributeValue, ElementId, Mutation, Template, TemplateAttribute, TemplateNode, }; -use dioxus_html::{event_bubbles, CompositionData, FormData, FormValue, MountedData}; +use dioxus_html::PlatformEventData; +use dioxus_html::{event_bubbles, MountedData}; use dioxus_interpreter_js::{get_node, minimal_bindings, save_template, Channel}; -use dioxus_html::{event_bubbles, MountedData, PlatformEventData}; -use dioxus_interpreter_js::get_node; -use dioxus_interpreter_js::{minimal_bindings, save_template, Channel}; use futures_channel::mpsc; use rustc_hash::FxHashMap; -use std::{any::Any, collections::HashMap, rc::Rc}; -use wasm_bindgen::{closure::Closure, prelude::wasm_bindgen, JsCast, JsValue}; use wasm_bindgen::{closure::Closure, JsCast, JsValue}; use web_sys::{Document, Element, Event}; @@ -264,185 +260,6 @@ impl WebsysDom { #[cfg(feature = "mounted")] for id in to_mount { - let node = get_node(id.0 as u32); - if let Some(element) = node.dyn_ref::() { - let data: MountedData = element.into(); - let data = Rc::new(data); - let _ = self.event_channel.unbounded_send(UiEvent { - name: "mounted".to_string(), - bubbles: false, - element: id, - data, - }); - } - } - } -} - -// 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. -pub fn virtual_event_from_websys_event(event: web_sys::Event, target: Element) -> Rc { - use dioxus_html::events::*; - - match event.type_().as_str() { - "copy" | "cut" | "paste" => Rc::new(ClipboardData {}), - "compositionend" | "compositionstart" | "compositionupdate" => { - make_composition_event(&event) - } - "keydown" | "keypress" | "keyup" => Rc::new(KeyboardData::from(event)), - "focus" | "blur" | "focusout" | "focusin" => Rc::new(FocusData {}), - - "change" | "input" | "invalid" | "reset" | "submit" => read_input_to_data(target), - - "click" | "contextmenu" | "dblclick" | "doubleclick" | "mousedown" | "mouseenter" - | "mouseleave" | "mousemove" | "mouseout" | "mouseover" | "mouseup" => { - Rc::new(MouseData::from(event)) - } - "drag" | "dragend" | "dragenter" | "dragexit" | "dragleave" | "dragover" | "dragstart" - | "drop" => { - let mouse = MouseData::from(event); - Rc::new(DragData { mouse }) - } - - "pointerdown" | "pointermove" | "pointerup" | "pointercancel" | "gotpointercapture" - | "lostpointercapture" | "pointerenter" | "pointerleave" | "pointerover" | "pointerout" => { - Rc::new(PointerData::from(event)) - } - "select" => Rc::new(SelectionData {}), - "touchcancel" | "touchend" | "touchmove" | "touchstart" => Rc::new(TouchData::from(event)), - - "scroll" => Rc::new(ScrollData {}), - "wheel" => Rc::new(WheelData::from(event)), - "animationstart" | "animationend" | "animationiteration" => { - Rc::new(AnimationData::from(event)) - } - "transitionend" => Rc::new(TransitionData::from(event)), - "abort" | "canplay" | "canplaythrough" | "durationchange" | "emptied" | "encrypted" - | "ended" | "loadeddata" | "loadedmetadata" | "loadstart" | "pause" | "play" - | "playing" | "progress" | "ratechange" | "seeked" | "seeking" | "stalled" | "suspend" - | "timeupdate" | "volumechange" | "waiting" => Rc::new(MediaData {}), - "error" => Rc::new(ImageData { load_error: true }), - "load" => Rc::new(ImageData { load_error: false }), - "toggle" => Rc::new(ToggleData {}), - - _ => Rc::new(()), - } -} - -fn make_composition_event(event: &Event) -> Rc { - let evt: &web_sys::CompositionEvent = event.dyn_ref().unwrap(); - Rc::new(CompositionData { - data: evt.data().unwrap_or_default(), - }) -} - -pub(crate) fn load_document() -> Document { - web_sys::window() - .expect("should have access to the Window") - .document() - .expect("should have access to the Document") -} - -fn read_input_to_data(target: Element) -> Rc { - // todo: these handlers might get really slow if the input box gets large and allocation pressure is heavy - // don't have a good solution with the serialized event problem - - let value: String = target - .dyn_ref() - .map(|input: &web_sys::HtmlInputElement| { - // todo: special case more input types - 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()) - }) - // select elements are NOT input events - because - why woudn't they be?? - .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"); - - let mut values = HashMap::new(); - - // try to fill in form values - if let Some(form) = target.dyn_ref::() { - let form_data = get_form_data(form); - for value in form_data.entries().into_iter().flatten() { - 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: Vec = - item_values.iter().filter_map(|v| v.as_string()).collect(); - - 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())); - } - } - } - } - } - - #[cfg(not(feature = "file_engine"))] - let files = None; - #[cfg(feature = "file_engine")] - let files = target - .dyn_ref() - .and_then(|input: &web_sys::HtmlInputElement| { - input.files().and_then(|files| { - #[allow(clippy::arc_with_non_send_sync)] - crate::file_engine::WebFileEngine::new(files) - .map(|f| std::sync::Arc::new(f) as std::sync::Arc) - }) - }); - - Rc::new(FormData { - value, - values, - files, - }) -} - -// web-sys does not expose the keys api for form data, so we need to manually bind to it -#[wasm_bindgen(inline_js = r#" - export function get_form_data(form) { - let values = new Map(); - - const formData = new FormData(form); - - for (let name of formData.keys()) { - const fieldType = form.elements[name].type; - - switch (fieldType) { - case "select-multiple": - values.set(name, formData.getAll(name)); - break; - - // add cases for fieldTypes that can hold multiple values here - default: - values.set(name, formData.get(name)); - break; - } self.send_mount_event(id); } } 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; From ac326e4dfdcdd9e35187d673cb930400cd08f296 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 8 Jan 2024 10:35:10 -0800 Subject: [PATCH 19/19] Fix clippy with features --- packages/html/src/events/form.rs | 33 +++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/packages/html/src/events/form.rs b/packages/html/src/events/form.rs index ee69104b9..ca1686cb7 100644 --- a/packages/html/src/events/form.rs +++ b/packages/html/src/events/form.rs @@ -1,13 +1,17 @@ -use std::{any::Any, collections::HashMap, fmt::Debug, sync::Arc}; +use std::{any::Any, collections::HashMap, fmt::Debug}; use dioxus_core::Event; -use serde::{Deserialize, Serialize}; pub type FormEvent = Event; /// A form value that may either be a list of values or a single value -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] -#[serde(untagged)] // this will serialize Text(String) -> String and VecText(Vec) to Vec +#[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), @@ -86,20 +90,23 @@ pub trait HasFormData: std::any::Any { fn as_any(&self) -> &dyn std::any::Any; } -fn convert_hashmap_to_json(hashmap: &HashMap) -> serde_json::Result -where - K: Serialize + std::hash::Hash + Eq, - V: Serialize, -{ - serde_json::to_string(hashmap) -} - 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"); @@ -176,7 +183,7 @@ impl HasFormData for SerializedFormData { fn files(&self) -> Option> { self.files .as_ref() - .map(|files| Arc::new(files.clone()) as _) + .map(|files| std::sync::Arc::new(files.clone()) as _) } fn as_any(&self) -> &dyn std::any::Any {