hirola_form/
lib.rs

1use std::collections::BTreeMap;
2
3use hirola_core::{
4    effect::EffectAttribute,
5    generic_node::EventListener,
6    prelude::{GenericNode, MutableBTreeMap},
7};
8use hirola_dom::effects::attr_mixin::XEffect;
9use hirola_dom::{node_ref::NodeRef, Dom};
10use wasm_bindgen::JsCast;
11use web_sys::{Event, HtmlInputElement};
12
13pub struct Form;
14
15impl EffectAttribute for Form {
16    type Handler = XEffect;
17    fn read_as_attr(&self) -> String {
18        "form".to_string()
19    }
20}
21
22/// Form plugin for hirola
23#[derive(Clone, Debug)]
24pub struct FormHandler<T: FormEntity> {
25    node_ref: NodeRef,
26    pub value: MutableBTreeMap<T::Columns, String>,
27}
28
29impl<T: FormEntity + Clone> FormHandler<T> {
30    /// Build a new reactive form
31    pub fn new(value: T) -> Self {
32        Self {
33            node_ref: NodeRef::new(),
34            value: MutableBTreeMap::with_values(value.into()),
35        }
36    }
37
38    pub fn update_field(&self, name: T::Columns, value: String) {
39        self.value.lock_mut().insert_cloned(name, value);
40    }
41
42    /// Get and cast a field value
43    pub fn get_value_by_field(&self, name: &T::Columns) -> Option<String> {
44        self.value.lock_ref().get(name).cloned()
45    }
46}
47impl<T: FormEntity + Clone> FormHandler<T> {
48    pub fn current(&self) -> T {
49        self.value.lock_ref().clone().into()
50    }
51}
52
53pub trait FormColumn {
54    fn name(&self) -> &str;
55}
56
57pub trait FormEntity:
58    From<BTreeMap<Self::Columns, String>> + Into<BTreeMap<Self::Columns, String>>
59{
60    type Columns: FormColumn + Copy + Ord;
61}
62
63impl<T: FormEntity + Clone> FormHandler<T>
64where
65    T::Columns: 'static,
66    T: 'static,
67{
68    pub fn bind(&self, column: T::Columns) -> Box<dyn FnOnce(&Dom)> {
69        let form = self.clone();
70        let cb = move |dom: &Dom| {
71            let f = form.clone();
72            let handler = Box::new(move |e: Event| {
73                let input = e
74                    .current_target()
75                    .unwrap()
76                    .dyn_into::<HtmlInputElement>()
77                    .unwrap();
78                let new_value = input.value();
79                f.update_field(column, new_value);
80            });
81            dom.event("input", handler);
82            let value: String = form.get_value_by_field(&column).unwrap_or_default();
83            dom.set_attribute("value", &value);
84
85            dom.set_attribute("name", &column.name());
86        };
87
88        Box::new(cb)
89    }
90
91    /// Get the reference for the form
92    pub fn node_ref(&self) -> NodeRef {
93        self.node_ref.clone()
94    }
95}