use std::{any::Any, collections::HashMap, fmt::Debug}; use dioxus_core::Event; pub type FormEvent = Event; pub struct FormData { inner: Box, } impl From for FormData { fn from(e: E) -> Self { Self { inner: Box::new(e) } } } 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() } } impl FormData { /// Create a new form event pub fn new(event: impl HasFormData + 'static) -> Self { Self { inner: Box::new(event), } } /// Get the value of the form event pub fn value(&self) -> String { self.inner.value() } /// Get the values of the form event pub fn values(&self) -> HashMap> { self.inner.values() } /// Get the files of the form event pub fn files(&self) -> Option> { self.inner.files() } /// Downcast this event to a concrete event type pub fn downcast(&self) -> Option<&T> { self.inner.as_any().downcast_ref::() } } /// An object that has all the data for a form event pub trait HasFormData: std::any::Any { fn value(&self) -> String { Default::default() } fn values(&self) -> HashMap> { Default::default() } fn files(&self) -> Option> { None } /// return self as Any fn as_any(&self) -> &dyn std::any::Any; } #[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>, } #[cfg(feature = "serialize")] impl SerializedFormData { /// Create a new serialized form data object pub fn new( value: String, values: HashMap>, files: Option>, ) -> Self { Self { value, values, files, } } /// Create a new serialized form data object from a traditional form data object pub async fn async_from(data: &FormData) -> Self { Self { value: data.value(), values: data.values(), files: match data.files() { Some(files) => { let mut resolved_files = HashMap::new(); for file in files.files() { let bytes = files.read_file(&file).await; resolved_files.insert(file, bytes.unwrap_or_default()); } Some(std::sync::Arc::new(SerializedFileEngine { files: resolved_files, })) } None => None, }, } } fn from_lossy(data: &FormData) -> Self { Self { value: data.value(), values: data.values(), files: None, } } } #[cfg(feature = "serialize")] impl HasFormData for SerializedFormData { fn value(&self) -> String { self.value.clone() } 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) } fn as_any(&self) -> &dyn std::any::Any { self } } #[cfg(feature = "serialize")] impl serde::Serialize for FormData { fn serialize(&self, serializer: S) -> Result { SerializedFormData::from_lossy(self).serialize(serializer) } } #[cfg(feature = "serialize")] impl<'de> serde::Deserialize<'de> for FormData { fn deserialize>(deserializer: D) -> Result { let data = SerializedFormData::deserialize(deserializer)?; Ok(Self { inner: Box::new(data), }) } } #[cfg(feature = "serialize")] /// A file engine that serializes files to bytes #[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Clone)] pub struct SerializedFileEngine { files: HashMap>, } #[cfg(feature = "serialize")] #[async_trait::async_trait(?Send)] impl FileEngine for SerializedFileEngine { fn files(&self) -> Vec { self.files.keys().cloned().collect() } async fn read_file(&self, file: &str) -> Option> { self.files.get(file).cloned() } async fn read_file_to_string(&self, file: &str) -> Option { self.read_file(file) .await .map(|bytes| String::from_utf8_lossy(&bytes).to_string()) } async fn get_native_file(&self, file: &str) -> Option> { self.read_file(file) .await .map(|val| Box::new(val) as Box) } } #[async_trait::async_trait(?Send)] pub trait FileEngine { // get a list of file names fn files(&self) -> Vec; // read a file to bytes async fn read_file(&self, file: &str) -> Option>; // read a file to string async fn read_file_to_string(&self, file: &str) -> Option; // returns a file in platform's native representation async fn get_native_file(&self, file: &str) -> Option>; } impl_event! { FormData; /// onchange onchange /// oninput handler oninput /// oninvalid oninvalid /// onreset onreset /// onsubmit onsubmit }