dioxus_web/events/
form.rs

1use std::{any::Any, collections::HashMap};
2
3use dioxus_html::{FormValue, HasFileData, HasFormData};
4use js_sys::Array;
5use wasm_bindgen::JsValue;
6use wasm_bindgen::{prelude::wasm_bindgen, JsCast};
7use web_sys::{Element, Event};
8
9use super::WebEventExt;
10
11pub(crate) struct WebFormData {
12    element: Element,
13    raw: Event,
14}
15
16impl WebEventExt for dioxus_html::FormData {
17    type WebEvent = Event;
18
19    #[inline(always)]
20    fn try_as_web_event(&self) -> Option<Self::WebEvent> {
21        self.downcast::<Event>().cloned()
22    }
23}
24
25impl WebFormData {
26    pub fn new(element: Element, raw: Event) -> Self {
27        Self { element, raw }
28    }
29}
30
31impl HasFormData for WebFormData {
32    fn value(&self) -> String {
33        let target = &self.element;
34        target
35        .dyn_ref()
36        .map(|input: &web_sys::HtmlInputElement| {
37            // todo: special case more input types
38            match input.type_().as_str() {
39                "checkbox" => {
40                    match input.checked() {
41                        true => "true".to_string(),
42                        false => "false".to_string(),
43                    }
44                },
45                _ => {
46                    input.value()
47                }
48            }
49        })
50        .or_else(|| {
51            target
52                .dyn_ref()
53                .map(|input: &web_sys::HtmlTextAreaElement| input.value())
54        })
55        // select elements are NOT input events - because - why woudn't they be??
56        .or_else(|| {
57            target
58                .dyn_ref()
59                .map(|input: &web_sys::HtmlSelectElement| input.value())
60        })
61        .or_else(|| {
62            target
63                .dyn_ref::<web_sys::HtmlElement>()
64                .unwrap()
65                .text_content()
66        })
67        .expect("only an InputElement or TextAreaElement or an element with contenteditable=true can have an oninput event listener")
68    }
69
70    fn values(&self) -> HashMap<String, FormValue> {
71        let mut values = HashMap::new();
72
73        fn insert_value(map: &mut HashMap<String, FormValue>, key: String, new_value: String) {
74            map.entry(key.clone()).or_default().0.push(new_value);
75        }
76
77        // try to fill in form values
78        if let Some(form) = self.element.dyn_ref::<web_sys::HtmlFormElement>() {
79            let form_data = get_form_data(form);
80            for value in form_data.entries().into_iter().flatten() {
81                if let Ok(array) = value.dyn_into::<Array>() {
82                    if let Some(name) = array.get(0).as_string() {
83                        if let Ok(item_values) = array.get(1).dyn_into::<Array>() {
84                            item_values
85                                .iter()
86                                .filter_map(|v| v.as_string())
87                                .for_each(|v| insert_value(&mut values, name.clone(), v));
88                        } else if let Ok(item_value) = array.get(1).dyn_into::<JsValue>() {
89                            insert_value(&mut values, name, item_value.as_string().unwrap());
90                        }
91                    }
92                }
93            }
94        } else if let Some(select) = self.element.dyn_ref::<web_sys::HtmlSelectElement>() {
95            // try to fill in select element values
96            let options = get_select_data(select);
97            values.insert("options".to_string(), FormValue(options));
98        }
99
100        values
101    }
102
103    fn as_any(&self) -> &dyn Any {
104        &self.raw as &dyn Any
105    }
106}
107
108impl HasFileData for WebFormData {
109    fn files(&self) -> Option<std::sync::Arc<dyn dioxus_html::FileEngine>> {
110        #[cfg(feature = "file_engine")]
111        {
112            let files = self
113                .element
114                .dyn_ref()
115                .and_then(|input: &web_sys::HtmlInputElement| {
116                    input.files().and_then(|files| {
117                        #[allow(clippy::arc_with_non_send_sync)]
118                        crate::file_engine::WebFileEngine::new(files).map(|f| {
119                            std::sync::Arc::new(f) as std::sync::Arc<dyn dioxus_html::FileEngine>
120                        })
121                    })
122                });
123
124            files
125        }
126        #[cfg(not(feature = "file_engine"))]
127        {
128            None
129        }
130    }
131}
132
133// web-sys does not expose the keys api for select data, so we need to manually bind to it
134#[wasm_bindgen(inline_js = r#"
135export function get_select_data(select) {
136    let values = [];
137    for (let i = 0; i < select.options.length; i++) {
138      let option = select.options[i];
139      if (option.selected) {
140        values.push(option.value.toString());
141      }
142    }
143
144    return values;
145}
146"#)]
147extern "C" {
148    fn get_select_data(select: &web_sys::HtmlSelectElement) -> Vec<String>;
149}
150
151// web-sys does not expose the keys api for form data, so we need to manually bind to it
152#[wasm_bindgen(inline_js = r#"
153export function get_form_data(form) {
154    let values = new Map();
155    const formData = new FormData(form);
156
157    for (let name of formData.keys()) {
158        values.set(name, formData.getAll(name));
159    }
160
161    return values;
162}
163"#)]
164extern "C" {
165    fn get_form_data(form: &web_sys::HtmlFormElement) -> js_sys::Map;
166}