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 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}