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::{prelude::wasm_bindgen, JsCast};
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        let target = &self.element;
32        target
33            .dyn_ref()
34            .map(|input: &web_sys::HtmlInputElement| {
35                // todo: special case more input types
36                match input.type_().as_str() {
37                    "checkbox" => {
38                        match input.checked() {
39                            true => "true".to_string(),
40                            false => "false".to_string(),
41                        }
42                    },
43                    _ => {
44                        input.value()
45                    }
46                }
47            })
48            .or_else(|| {
49                target
50                    .dyn_ref()
51                    .map(|input: &web_sys::HtmlTextAreaElement| input.value())
52            })
53            // select elements are NOT input events - because - why woudn't they be??
54            .or_else(|| {
55                target
56                    .dyn_ref()
57                    .map(|input: &web_sys::HtmlSelectElement| input.value())
58            })
59            .or_else(|| {
60                target
61                    .dyn_ref::<web_sys::HtmlElement>()
62                    .unwrap()
63                    .text_content()
64            })
65            .expect("only an InputElement or TextAreaElement or an element with contenteditable=true can have an oninput event listener")
66    }
67
68    fn values(&self) -> Vec<(String, FormValue)> {
69        let mut values = Vec::new();
70
71        // try to fill in form values
72        if let Some(form) = self.element.dyn_ref::<web_sys::HtmlFormElement>() {
73            let form_data = web_sys::FormData::new_with_form(form).unwrap();
74
75            for entry in form_data.entries().into_iter().flatten() {
76                if let Ok(array) = entry.dyn_into::<Array>() {
77                    if let Some(name) = array.get(0).as_string() {
78                        let value = array.get(1);
79                        if let Some(file) = value.dyn_ref::<web_sys::File>() {
80                            if file.name().is_empty() {
81                                values.push((name, FormValue::File(None)));
82                            } else {
83                                let data =
84                                    WebFileData::new(file.clone(), FileReader::new().unwrap());
85                                let as_file = FileData::new(data);
86
87                                values.push((name, FormValue::File(Some(as_file))));
88                            }
89                        } else if let Some(s) = value.as_string() {
90                            values.push((name, FormValue::Text(s)));
91                        }
92                    }
93                }
94            }
95        } else if let Some(select) = self.element.dyn_ref::<web_sys::HtmlSelectElement>() {
96            // try to fill in select element values
97            let options = get_select_data(select);
98            for option in &options {
99                values.push((select.name(), FormValue::Text(option.clone())));
100            }
101        }
102
103        values
104    }
105
106    fn as_any(&self) -> &dyn Any {
107        &self.event as &dyn Any
108    }
109
110    fn valid(&self) -> bool {
111        self.event
112            .target()
113            .and_then(|t| t.dyn_into::<web_sys::HtmlInputElement>().ok())
114            .map(|input| input.check_validity())
115            .unwrap_or(true)
116    }
117}
118
119impl HasFileData for WebFormData {
120    fn files(&self) -> Vec<FileData> {
121        use wasm_bindgen::JsCast;
122        self.event
123            .target()
124            .and_then(|t| t.dyn_into::<web_sys::HtmlInputElement>().ok())
125            .and_then(|input| input.files())
126            .map(crate::files::WebFileEngine::new)
127            .map(|engine| engine.to_files())
128            .unwrap_or_default()
129    }
130}
131
132// web-sys does not expose the keys api for select data, so we need to manually bind to it
133#[wasm_bindgen(inline_js = r#"
134export function get_select_data(select) {
135    let values = [];
136    for (let i = 0; i < select.options.length; i++) {
137      let option = select.options[i];
138      if (option.selected) {
139        values.push(option.value.toString());
140      }
141    }
142
143    return values;
144}
145"#)]
146extern "C" {
147    fn get_select_data(select: &web_sys::HtmlSelectElement) -> Vec<String>;
148}