dioxus_html/events/
form.rs1use 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#[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 pub fn as_slice(&self) -> &[String] {
24 &self.0
25 }
26
27 pub fn as_value(&self) -> String {
29 self.0.first().unwrap().clone()
30 }
31
32 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
44pub 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 pub fn new(event: impl HasFormData + 'static) -> Self {
74 Self {
75 inner: Box::new(event),
76 }
77 }
78
79 pub fn value(&self) -> String {
81 self.inner.value()
82 }
83
84 pub fn parsed<T>(&self) -> Result<T, T::Err>
86 where
87 T: std::str::FromStr,
88 {
89 self.value().parse()
90 }
91
92 pub fn checked(&self) -> bool {
97 self.value().parse().unwrap_or(false)
98 }
99
100 pub fn values(&self) -> HashMap<String, FormValue> {
104 self.inner.values()
105 }
106
107 pub fn files(&self) -> Option<std::sync::Arc<dyn crate::file_data::FileEngine>> {
109 self.inner.files()
110 }
111
112 #[inline(always)]
114 pub fn downcast<T: 'static>(&self) -> Option<&T> {
115 self.inner.as_any().downcast_ref::<T>()
116 }
117
118 pub fn valid(&self) -> bool {
120 self.inner.value().is_empty()
121 }
122}
123
124pub 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 fn as_any(&self) -> &dyn std::any::Any;
140}
141
142impl FormData {
143 #[cfg(feature = "serialize")]
144 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#[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 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 pub fn with_files(mut self, files: crate::file_data::SerializedFileEngine) -> Self {
197 self.files = Some(files);
198 self
199 }
200
201 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
293
294 oninput
339
340 oninvalid
342
343 onreset
345
346 onsubmit
348}