urlap/
lib.rs

1use std::collections::HashMap;
2use std::fmt::Debug;
3
4use leptos::{
5    ev::Event,
6    prelude::{Get, RwSignal, Signal, Update, event_target_value},
7};
8use validator::Validate;
9use wasm_bindgen::JsCast;
10use web_sys::{HtmlInputElement, SubmitEvent};
11
12pub trait FormStruct: Clone + Debug + Validate {
13    fn get(&self, name: &str) -> Option<String>;
14    fn set(&mut self, name: &str, value: &str);
15}
16
17#[derive(Clone)]
18pub struct Form<T: Clone + Default + FormStruct + Send + Sync + 'static> {
19    values: RwSignal<T>,
20    errors: RwSignal<HashMap<String, Option<String>>>,
21}
22
23impl<T: Clone + Default + FormStruct + Send + Sync + 'static> Default for Form<T> {
24    fn default() -> Self {
25        Self::new()
26    }
27}
28
29impl<T: Clone + Default + FormStruct + Send + Sync + 'static> Form<T> {
30    pub fn new() -> Form<T> {
31        let values: RwSignal<T> = RwSignal::new(Default::default());
32        let errors = RwSignal::new(HashMap::new());
33
34        Self { values, errors }
35    }
36
37    pub fn values(&self) -> Signal<T> {
38        self.values.into()
39    }
40
41    pub fn errors(&self) -> Signal<HashMap<String, Option<String>>> {
42        self.errors.into()
43    }
44
45    pub fn value(&self, field: &str) -> Signal<String> {
46        let values = self.values.get();
47        values.get(field).unwrap_or_default().into()
48    }
49
50    pub fn error(&self, field: &str) -> Signal<Option<String>> {
51        self.errors
52            .get()
53            .get(field)
54            .cloned()
55            .unwrap_or_default()
56            .into()
57    }
58
59    /// Input Handler for Form Inputs of type [`HtmlInputElement`]
60    pub fn handle_input(&self) -> impl Fn(Event) + Copy + 'static {
61        let values = self.values;
62
63        move |ev: Event| {
64            if let Some(target) = ev.target() {
65                if let Ok(el) = target.dyn_into::<HtmlInputElement>() {
66                    let name = el.name();
67
68                    values.update(|values| {
69                        let value = event_target_value(&ev);
70                        values.set(&name, &value);
71                    });
72                }
73            }
74        }
75    }
76
77    pub fn handle_submit<F: Fn(T)>(&self, cb: F) -> impl Fn(SubmitEvent) {
78        let errors = self.errors;
79        let values = self.values;
80
81        move |ev| {
82            ev.prevent_default();
83
84            if let Err(validation_err) = values.get().validate() {
85                validation_err
86                    .field_errors()
87                    .iter()
88                    .for_each(|(field, f_errors)| {
89                        f_errors.iter().for_each(|err| {
90                            errors.update(|e| {
91                                e.insert(field.to_string(), Some(err.to_string()));
92                            });
93                        });
94                    });
95
96                return;
97            }
98
99            cb(values.get());
100        }
101    }
102}