Skip to main content

dioxus_web/events/
form.rs

1use super::WebEventExt;
2use crate::WebFileData;
3use dioxus_html::{FileData, FormValue, HasFileData, HasFormData};
4use js_sys::Array;
5use std::any::Any;
6use wasm_bindgen::{JsCast, prelude::wasm_bindgen};
7use web_sys::{Element, Event, FileReader};
8
9pub(crate) struct WebFormData {
10    element: Element,
11    event: Event,
12}
13
14impl WebEventExt for dioxus_html::FormData {
15    type WebEvent = Event;
16
17    #[inline(always)]
18    fn try_as_web_event(&self) -> Option<Self::WebEvent> {
19        self.downcast::<Event>().cloned()
20    }
21}
22
23impl WebFormData {
24    pub fn new(element: Element, event: Event) -> Self {
25        Self { element, event }
26    }
27}
28
29impl HasFormData for WebFormData {
30    fn value(&self) -> String {
31        super::editable_element_value(&self.element)
32            .expect("only an InputElement or TextAreaElement or an element with contenteditable=true can have an oninput event listener")
33    }
34
35    fn values(&self) -> Vec<(String, FormValue)> {
36        let mut values = Vec::new();
37
38        // try to fill in form values
39        if let Some(form) = self.element.dyn_ref::<web_sys::HtmlFormElement>() {
40            let form_data = web_sys::FormData::new_with_form(form).unwrap();
41
42            for entry in form_data.entries().into_iter().flatten() {
43                if let Ok(array) = entry.dyn_into::<Array>()
44                    && let Some(name) = array.get(0).as_string()
45                {
46                    let value = array.get(1);
47                    if let Some(file) = value.dyn_ref::<web_sys::File>() {
48                        if file.name().is_empty() {
49                            values.push((name, FormValue::File(None)));
50                        } else {
51                            let data = WebFileData::new(file.clone(), FileReader::new().unwrap());
52                            let as_file = FileData::new(data);
53
54                            values.push((name, FormValue::File(Some(as_file))));
55                        }
56                    } else if let Some(s) = value.as_string() {
57                        values.push((name, FormValue::Text(s)));
58                    }
59                }
60            }
61        } else if let Some(select) = self.element.dyn_ref::<web_sys::HtmlSelectElement>() {
62            // try to fill in select element values
63            let options = get_select_data(select);
64            for option in &options {
65                values.push((select.name(), FormValue::Text(option.clone())));
66            }
67        }
68
69        values
70    }
71
72    fn as_any(&self) -> &dyn Any {
73        &self.event as &dyn Any
74    }
75
76    fn valid(&self) -> bool {
77        self.event
78            .target()
79            .and_then(|t| t.dyn_into::<web_sys::HtmlInputElement>().ok())
80            .map(|input| input.check_validity())
81            .unwrap_or(true)
82    }
83}
84
85impl HasFileData for WebFormData {
86    fn files(&self) -> Vec<FileData> {
87        use wasm_bindgen::JsCast;
88        self.event
89            .target()
90            .and_then(|t| t.dyn_into::<web_sys::HtmlInputElement>().ok())
91            .and_then(|input| input.files())
92            .map(crate::files::WebFileEngine::new)
93            .map(|engine| engine.to_files())
94            .unwrap_or_default()
95    }
96}
97
98// web-sys does not expose the keys api for select data, so we need to manually bind to it
99#[wasm_bindgen(inline_js = r#"
100export function get_select_data(select) {
101    let values = [];
102    for (let i = 0; i < select.options.length; i++) {
103      let option = select.options[i];
104      if (option.selected) {
105        values.push(option.value.toString());
106      }
107    }
108
109    return values;
110}
111"#)]
112extern "C" {
113    fn get_select_data(select: &web_sys::HtmlSelectElement) -> Vec<String>;
114}