dioxus_html/events/
form.rs

1use crate::file_data::HasFileData;
2use std::{collections::HashMap, fmt::Debug, ops::Deref};
3
4use dioxus_core::Event;
5
6pub type FormEvent = Event<FormData>;
7
8/// A form value that may either be a list of values or a single value
9#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
10#[derive(Debug, Default, Clone, PartialEq)]
11pub struct FormValue(pub Vec<String>);
12
13impl Deref for FormValue {
14    type Target = [String];
15
16    fn deref(&self) -> &Self::Target {
17        self.as_slice()
18    }
19}
20
21impl FormValue {
22    /// Convenient way to represent Value as slice
23    pub fn as_slice(&self) -> &[String] {
24        &self.0
25    }
26
27    /// Return the first value, panicking if there are none
28    pub fn as_value(&self) -> String {
29        self.0.first().unwrap().clone()
30    }
31
32    /// Convert into [`Vec<String>`]
33    pub fn to_vec(self) -> Vec<String> {
34        self.0.clone()
35    }
36}
37
38impl PartialEq<str> for FormValue {
39    fn eq(&self, other: &str) -> bool {
40        self.0.len() == 1 && self.0.first().map(|s| s.as_str()) == Some(other)
41    }
42}
43
44/* DOMEvent:  Send + SyncTarget relatedTarget */
45pub struct FormData {
46    inner: Box<dyn HasFormData>,
47}
48
49impl<E: HasFormData> From<E> for FormData {
50    fn from(e: E) -> Self {
51        Self { inner: Box::new(e) }
52    }
53}
54
55impl PartialEq for FormData {
56    fn eq(&self, other: &Self) -> bool {
57        self.value() == other.value() && self.values() == other.values()
58    }
59}
60
61impl Debug for FormData {
62    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63        f.debug_struct("FormEvent")
64            .field("value", &self.value())
65            .field("values", &self.values())
66            .field("valid", &self.valid())
67            .finish()
68    }
69}
70
71impl FormData {
72    /// Create a new form event
73    pub fn new(event: impl HasFormData + 'static) -> Self {
74        Self {
75            inner: Box::new(event),
76        }
77    }
78
79    /// Get the value of the form event
80    pub fn value(&self) -> String {
81        self.inner.value()
82    }
83
84    /// Get the value of the form event as a parsed type
85    pub fn parsed<T>(&self) -> Result<T, T::Err>
86    where
87        T: std::str::FromStr,
88    {
89        self.value().parse()
90    }
91
92    /// Try to parse the value as a boolean
93    ///
94    /// Returns false if the value is not a boolean, or if it is false!
95    /// Does not verify anything about the event itself, use with caution
96    pub fn checked(&self) -> bool {
97        self.value().parse().unwrap_or(false)
98    }
99
100    /// Collect all the named form values from the containing form.
101    ///
102    /// Every input must be named!
103    pub fn values(&self) -> HashMap<String, FormValue> {
104        self.inner.values()
105    }
106
107    /// Get the files of the form event
108    pub fn files(&self) -> Option<std::sync::Arc<dyn crate::file_data::FileEngine>> {
109        self.inner.files()
110    }
111
112    /// Downcast this event to a concrete event type
113    #[inline(always)]
114    pub fn downcast<T: 'static>(&self) -> Option<&T> {
115        self.inner.as_any().downcast_ref::<T>()
116    }
117
118    /// Did this form pass its own validation?
119    pub fn valid(&self) -> bool {
120        self.inner.value().is_empty()
121    }
122}
123
124/// An object that has all the data for a form event
125pub trait HasFormData: HasFileData + std::any::Any {
126    fn value(&self) -> String {
127        Default::default()
128    }
129
130    fn valid(&self) -> bool {
131        true
132    }
133
134    fn values(&self) -> HashMap<String, FormValue> {
135        Default::default()
136    }
137
138    /// return self as Any
139    fn as_any(&self) -> &dyn std::any::Any;
140}
141
142impl FormData {
143    #[cfg(feature = "serialize")]
144    /// Parse the values into a struct with one field per value
145    pub fn parsed_values<T>(&self) -> Result<T, serde_json::Error>
146    where
147        T: serde::de::DeserializeOwned,
148    {
149        use serde::Serialize;
150
151        fn convert_hashmap_to_json<K, V>(hashmap: &HashMap<K, V>) -> serde_json::Result<String>
152        where
153            K: Serialize + std::hash::Hash + Eq,
154            V: Serialize,
155        {
156            serde_json::to_string(hashmap)
157        }
158
159        let parsed_json =
160            convert_hashmap_to_json(&self.values()).expect("Failed to parse values to JSON");
161
162        serde_json::from_str(&parsed_json)
163    }
164}
165
166#[cfg(feature = "serialize")]
167/// A serialized form data object
168#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Clone)]
169pub struct SerializedFormData {
170    #[serde(default)]
171    value: String,
172
173    #[serde(default)]
174    values: HashMap<String, FormValue>,
175
176    #[serde(default)]
177    valid: bool,
178
179    #[serde(default)]
180    files: Option<crate::file_data::SerializedFileEngine>,
181}
182
183#[cfg(feature = "serialize")]
184impl SerializedFormData {
185    /// Create a new serialized form data object
186    pub fn new(value: String, values: HashMap<String, FormValue>) -> Self {
187        Self {
188            value,
189            values,
190            valid: true,
191            files: None,
192        }
193    }
194
195    /// Add files to the serialized form data object
196    pub fn with_files(mut self, files: crate::file_data::SerializedFileEngine) -> Self {
197        self.files = Some(files);
198        self
199    }
200
201    /// Create a new serialized form data object from a traditional form data object
202    pub async fn async_from(data: &FormData) -> Self {
203        Self {
204            value: data.value(),
205            values: data.values(),
206            valid: data.valid(),
207            files: {
208                match data.files() {
209                    Some(files) => {
210                        let mut resolved_files = HashMap::new();
211
212                        for file in files.files() {
213                            let bytes = files.read_file(&file).await;
214                            resolved_files.insert(file, bytes.unwrap_or_default());
215                        }
216
217                        Some(crate::file_data::SerializedFileEngine {
218                            files: resolved_files,
219                        })
220                    }
221                    None => None,
222                }
223            },
224        }
225    }
226
227    fn from_lossy(data: &FormData) -> Self {
228        Self {
229            value: data.value(),
230            values: data.values(),
231            valid: data.valid(),
232            files: None,
233        }
234    }
235}
236
237#[cfg(feature = "serialize")]
238impl HasFormData for SerializedFormData {
239    fn value(&self) -> String {
240        self.value.clone()
241    }
242
243    fn values(&self) -> HashMap<String, FormValue> {
244        self.values.clone()
245    }
246
247    fn valid(&self) -> bool {
248        self.valid
249    }
250
251    fn as_any(&self) -> &dyn std::any::Any {
252        self
253    }
254}
255
256#[cfg(feature = "serialize")]
257impl HasFileData for SerializedFormData {
258    fn files(&self) -> Option<std::sync::Arc<dyn crate::FileEngine>> {
259        self.files
260            .as_ref()
261            .map(|files| std::sync::Arc::new(files.clone()) as _)
262    }
263}
264
265impl HasFileData for FormData {
266    fn files(&self) -> Option<std::sync::Arc<dyn crate::FileEngine>> {
267        self.inner.files()
268    }
269}
270
271#[cfg(feature = "serialize")]
272impl serde::Serialize for FormData {
273    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
274        SerializedFormData::from_lossy(self).serialize(serializer)
275    }
276}
277
278#[cfg(feature = "serialize")]
279impl<'de> serde::Deserialize<'de> for FormData {
280    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
281        let data = SerializedFormData::deserialize(deserializer)?;
282        Ok(Self {
283            inner: Box::new(data),
284        })
285    }
286}
287
288impl_event! {
289    FormData;
290
291    /// onchange
292    onchange
293
294    /// The `oninput` event is fired when the value of a `<input>`, `<select>`, or `<textarea>` element is changed.
295    ///
296    /// There are two main approaches to updating your input element:
297    /// 1) Controlled inputs directly update the value of the input element as the user interacts with the element
298    ///
299    /// ```rust
300    /// use dioxus::prelude::*;
301    ///
302    /// fn App() -> Element {
303    ///     let mut value = use_signal(|| "hello world".to_string());
304    ///
305    ///     rsx! {
306    ///         input {
307    ///             // We directly set the value of the input element to our value signal
308    ///             value: "{value}",
309    ///             // The `oninput` event handler will run every time the user changes the value of the input element
310    ///             // We can set the `value` signal to the new value of the input element
311    ///             oninput: move |event| value.set(event.value())
312    ///         }
313    ///         // Since this is a controlled input, we can also update the value of the input element directly
314    ///         button {
315    ///             onclick: move |_| value.write().clear(),
316    ///             "Clear"
317    ///         }
318    ///     }
319    /// }
320    /// ```
321    ///
322    /// 2) Uncontrolled inputs just read the value of the input element as it changes
323    ///
324    /// ```rust
325    /// use dioxus::prelude::*;
326    ///
327    /// fn App() -> Element {
328    ///     rsx! {
329    ///         input {
330    ///             // In uncontrolled inputs, we don't set the value of the input element directly
331    ///             // But you can still read the value of the input element
332    ///             oninput: move |event| println!("{}", event.value()),
333    ///         }
334    ///         // Since we don't directly control the value of the input element, we can't easily modify it
335    ///     }
336    /// }
337    /// ```
338    oninput
339
340    /// oninvalid
341    oninvalid
342
343    /// onreset
344    onreset
345
346    /// onsubmit
347    onsubmit
348}