Browse Source

Fix form events with select multiple

Evan Almloff 2 years ago
parent
commit
f1f7517b88
3 changed files with 45 additions and 57 deletions
  1. 1 1
      packages/html/src/events/form.rs
  2. 15 26
      packages/interpreter/src/interpreter.js
  3. 29 30
      packages/web/src/dom.rs

+ 1 - 1
packages/html/src/events/form.rs

@@ -10,7 +10,7 @@ pub type FormEvent = Event<FormData>;
 pub struct FormData {
     pub value: String,
 
-    pub values: HashMap<String, String>,
+    pub values: HashMap<String, Vec<String>>,
 
     #[cfg_attr(feature = "serialize", serde(skip))]
     pub files: Option<std::sync::Arc<dyn FileEngine>>,

+ 15 - 26
packages/interpreter/src/interpreter.js

@@ -17,8 +17,7 @@ class ListenerMap {
       } else {
         this.global[event_name].active++;
       }
-    }
-    else {
+    } else {
       const id = element.getAttribute("data-dioxus-id");
       if (!this.local[id]) {
         this.local[id] = {};
@@ -32,11 +31,13 @@ class ListenerMap {
     if (bubbles) {
       this.global[event_name].active--;
       if (this.global[event_name].active === 0) {
-        this.root.removeEventListener(event_name, this.global[event_name].callback);
+        this.root.removeEventListener(
+          event_name,
+          this.global[event_name].callback
+        );
         delete this.global[event_name];
       }
-    }
-    else {
+    } else {
       const id = element.getAttribute("data-dioxus-id");
       delete this.local[id][event_name];
       if (this.local[id].length === 0) {
@@ -143,8 +144,7 @@ class Interpreter {
   SetAttribute(id, field, value, ns) {
     if (value === null) {
       this.RemoveAttribute(id, field, ns);
-    }
-    else {
+    } else {
       const node = this.nodes[id];
       this.SetAttributeInner(node, field, value, ns);
     }
@@ -342,7 +342,6 @@ class Interpreter {
         this.RemoveEventListener(edit.id, edit.name);
         break;
       case "NewEventListener":
-
         let bubbles = event_bubbles(edit.name);
 
         // this handler is only provided on desktop implementations since this
@@ -360,7 +359,10 @@ class Interpreter {
               let a_element = target.closest("a");
               if (a_element != null) {
                 event.preventDefault();
-                if (shouldPreventDefault !== `onclick` && a_element.getAttribute(`dioxus-prevent-default`) !== `onclick`) {
+                if (
+                  shouldPreventDefault !== `onclick` &&
+                  a_element.getAttribute(`dioxus-prevent-default`) !== `onclick`
+                ) {
                   const href = a_element.getAttribute("href");
                   if (href !== "" && href !== null && href !== undefined) {
                     window.ipc.postMessage(
@@ -404,23 +406,10 @@ class Interpreter {
               target.tagName === "FORM" &&
               (event.type === "submit" || event.type === "input")
             ) {
-              for (let x = 0; x < target.elements.length; x++) {
-                let element = target.elements[x];
-                let name = element.getAttribute("name");
-                if (name != null) {
-                  if (element.getAttribute("type") === "checkbox") {
-                    // @ts-ignore
-                    contents.values[name] = element.checked ? "true" : "false";
-                  } else if (element.getAttribute("type") === "radio") {
-                    if (element.checked) {
-                      contents.values[name] = element.value;
-                    }
-                  } else {
-                    // @ts-ignore
-                    contents.values[name] =
-                      element.value ?? element.textContent;
-                  }
-                }
+              const formData = new FormData(target);
+
+              for (let name of formData.keys()) {
+                contents.values[name] = formData.getAll(name);
               }
             }
 

+ 29 - 30
packages/web/src/dom.rs

@@ -13,9 +13,10 @@ use dioxus_core::{
 use dioxus_html::{event_bubbles, CompositionData, FormData};
 use dioxus_interpreter_js::{save_template, Channel};
 use futures_channel::mpsc;
+use js_sys::Array;
 use rustc_hash::FxHashMap;
 use std::{any::Any, rc::Rc};
-use wasm_bindgen::{closure::Closure, JsCast};
+use wasm_bindgen::{closure::Closure, prelude::wasm_bindgen, JsCast};
 use web_sys::{Document, Element, Event, HtmlElement};
 
 use crate::Config;
@@ -325,35 +326,16 @@ fn read_input_to_data(target: Element) -> Rc<FormData> {
 
     // try to fill in form values
     if let Some(form) = target.dyn_ref::<web_sys::HtmlFormElement>() {
-        let elements = form.elements();
-        for x in 0..elements.length() {
-            let element = elements.item(x).unwrap();
-            if let Some(name) = element.get_attribute("name") {
-                let value: Option<String> = element
-                    .dyn_ref()
-                    .map(|input: &web_sys::HtmlInputElement| {
-                        match input.type_().as_str() {
-                            "checkbox" => {
-                                match input.checked() {
-                                    true => Some("true".to_string()),
-                                    false => Some("false".to_string()),
-                                }
-                            },
-                            "radio" => {
-                                match input.checked() {
-                                    true => Some(input.value()),
-                                    false => None,
-                                }
-                            }
-                            _ => Some(input.value())
-                        }
-                    })
-                    .or_else(|| element.dyn_ref().map(|input: &web_sys::HtmlTextAreaElement| Some(input.value())))
-                    .or_else(|| element.dyn_ref().map(|input: &web_sys::HtmlSelectElement| Some(input.value())))
-                    .or_else(|| Some(element.dyn_ref::<web_sys::HtmlElement>().unwrap().text_content()))
-                    .expect("only an InputElement or TextAreaElement or an element with contenteditable=true can have an oninput event listener");
-                if let Some(value) = value {
-                    values.insert(name, value);
+        let form_data = get_form_data(form);
+        for value in form_data.entries().into_iter().flatten() {
+            if let Ok(array) = value.dyn_into::<Array>() {
+                if let Some(name) = array.get(0).as_string() {
+                    if let Ok(item_values) = array.get(1).dyn_into::<Array>() {
+                        let item_values =
+                            item_values.iter().filter_map(|v| v.as_string()).collect();
+
+                        values.insert(name, item_values);
+                    }
                 }
             }
         }
@@ -366,6 +348,23 @@ fn read_input_to_data(target: Element) -> Rc<FormData> {
     })
 }
 
+// web-sys does not expose the keys api for form data, so we need to manually bind to it
+#[wasm_bindgen(inline_js = r#"
+    export function get_form_data(form) {
+        let values = new Map();
+        const formData = new FormData(form);
+
+        for (let name of formData.keys()) {
+            values.set(name, formData.getAll(name));
+        }
+
+        return values;
+    }
+"#)]
+extern "C" {
+    fn get_form_data(form: &web_sys::HtmlFormElement) -> js_sys::Map;
+}
+
 fn walk_event_for_id(event: &web_sys::Event) -> Option<(ElementId, web_sys::Element)> {
     let mut target = event
         .target()