rooting_forms/
impl_str.rs

1use {
2    crate::{
3        css::{
4            err_el,
5            ATTR_LABEL,
6            CSS_CLASS_SMALL_INPUT,
7            CSS_CLASS_TEXT,
8        },
9        FormElements,
10        FormState,
11        FormWith,
12    },
13    gloo::timers::callback::Timeout,
14    rooting::{
15        el,
16        El,
17    },
18    std::{
19        cell::RefCell,
20        convert::Infallible,
21        fmt::Display,
22        str::FromStr,
23    },
24    wasm_bindgen::JsCast,
25    web_sys::{
26        HtmlInputElement,
27        HtmlTextAreaElement,
28    },
29};
30
31/// A minimal string wrapper that creates a textarea form input.
32#[derive(Clone)]
33#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
34pub struct BigString(pub String);
35
36impl FromStr for BigString {
37    type Err = Infallible;
38
39    fn from_str(s: &str) -> Result<Self, Self::Err> {
40        return Ok(BigString(s.to_string()));
41    }
42}
43
44struct BigStringFormState {
45    el: El,
46}
47
48impl FormState<BigString> for BigStringFormState {
49    fn parse(&self) -> Result<BigString, ()> {
50        return Ok(BigString(self.el.raw().dyn_ref::<HtmlTextAreaElement>().unwrap().value()));
51    }
52}
53
54impl<C> FormWith<C> for BigString {
55    fn new_form_with_(
56        _context: &C,
57        field: &str,
58        from: Option<&Self>,
59        _depth: usize,
60    ) -> (FormElements, Box<dyn FormState<Self>>) {
61        let textarea =
62            el("div")
63                .classes(&[CSS_CLASS_SMALL_INPUT, CSS_CLASS_TEXT])
64                .attr(ATTR_LABEL, field)
65                .attr("contenteditable", "plaintext-only")
66                .text(from.map(|x| x.0.as_str()).unwrap_or(""));
67        return (FormElements {
68            error: None,
69            elements: vec![textarea.clone()],
70        }, Box::new(BigStringFormState { el: textarea }));
71    }
72}
73
74/// A helper form type for rust types that implement `FromStr`.
75pub struct FromStrFormState {
76    el: El,
77    error_el: El,
78}
79
80impl FromStrFormState {
81    pub fn new<
82        E: Display,
83        T: FromStr<Err = E>,
84    >(label: &str, initial_value: &str) -> (FormElements, Box<dyn FormState<T>>) {
85        let error_el = err_el();
86        let input_el =
87            el("div")
88                .classes(&[CSS_CLASS_SMALL_INPUT, CSS_CLASS_TEXT])
89                .attr(ATTR_LABEL, label)
90                .attr("contenteditable", "plaintext-only")
91                .attr("value", initial_value)
92                .on("input", {
93                    let error_el = error_el.clone();
94                    let debounce = RefCell::new(None);
95                    move |ev| {
96                        error_el.ref_text("");
97                        let text = ev.target().unwrap().dyn_ref::<HtmlInputElement>().unwrap().value();
98                        *debounce.borrow_mut() = Some(Timeout::new(300, {
99                            let error_el = error_el.clone();
100                            move || {
101                                if text.len() >= 1 {
102                                    match T::from_str(&text) {
103                                        Err(e) => {
104                                            error_el.ref_text(&e.to_string());
105                                            return;
106                                        },
107                                        _ => { },
108                                    }
109                                }
110                            }
111                        }));
112                    }
113                });
114        return (FormElements {
115            error: Some(error_el.clone()),
116            elements: vec![input_el.clone()],
117        }, Box::new(FromStrFormState {
118            el: input_el,
119            error_el: error_el,
120        }));
121    }
122}
123
124impl<E: Display, T: FromStr<Err = E>> FormState<T> for FromStrFormState {
125    fn parse(&self) -> Result<T, ()> {
126        match T::from_str(&self.el.raw().dyn_ref::<HtmlInputElement>().unwrap().value()) {
127            Ok(v) => {
128                self.error_el.ref_text("");
129                return Ok(v);
130            },
131            Err(e) => {
132                self.error_el.ref_text(&e.to_string());
133                return Err(());
134            },
135        }
136    }
137}
138
139impl<C> FormWith<C> for String {
140    fn new_form_with_(
141        _context: &C,
142        field: &str,
143        from: Option<&Self>,
144        _depth: usize,
145    ) -> (FormElements, Box<dyn FormState<Self>>) {
146        return FromStrFormState::new::<_, String>(field, from.as_ref().map(|x| x.as_str()).unwrap_or(""));
147    }
148}
149
150impl<C> FormWith<C> for u8 {
151    fn new_form_with_(
152        _context: &C,
153        field: &str,
154        from: Option<&Self>,
155        _depth: usize,
156    ) -> (FormElements, Box<dyn FormState<Self>>) {
157        return FromStrFormState::new::<_, Self>(
158            field,
159            from.map(|x| x.to_string()).as_ref().map(|x| x.as_str()).unwrap_or(""),
160        );
161    }
162}
163
164impl<C> FormWith<C> for u16 {
165    fn new_form_with_(
166        _context: &C,
167        field: &str,
168        from: Option<&Self>,
169        _depth: usize,
170    ) -> (FormElements, Box<dyn FormState<Self>>) {
171        return FromStrFormState::new::<_, Self>(
172            field,
173            from.map(|x| x.to_string()).as_ref().map(|x| x.as_str()).unwrap_or(""),
174        );
175    }
176}
177
178impl<C> FormWith<C> for u32 {
179    fn new_form_with_(
180        _context: &C,
181        field: &str,
182        from: Option<&Self>,
183        _depth: usize,
184    ) -> (FormElements, Box<dyn FormState<Self>>) {
185        return FromStrFormState::new::<_, Self>(
186            field,
187            from.map(|x| x.to_string()).as_ref().map(|x| x.as_str()).unwrap_or(""),
188        );
189    }
190}
191
192impl<C> FormWith<C> for u64 {
193    fn new_form_with_(
194        _context: &C,
195        field: &str,
196        from: Option<&Self>,
197        _depth: usize,
198    ) -> (FormElements, Box<dyn FormState<Self>>) {
199        return FromStrFormState::new::<_, Self>(
200            field,
201            from.map(|x| x.to_string()).as_ref().map(|x| x.as_str()).unwrap_or(""),
202        );
203    }
204}
205
206impl<C> FormWith<C> for i8 {
207    fn new_form_with_(
208        _context: &C,
209        field: &str,
210        from: Option<&Self>,
211        _depth: usize,
212    ) -> (FormElements, Box<dyn FormState<Self>>) {
213        return FromStrFormState::new::<_, Self>(
214            field,
215            from.map(|x| x.to_string()).as_ref().map(|x| x.as_str()).unwrap_or(""),
216        );
217    }
218}
219
220impl<C> FormWith<C> for i16 {
221    fn new_form_with_(
222        _context: &C,
223        field: &str,
224        from: Option<&Self>,
225        _depth: usize,
226    ) -> (FormElements, Box<dyn FormState<Self>>) {
227        return FromStrFormState::new::<_, Self>(
228            field,
229            from.map(|x| x.to_string()).as_ref().map(|x| x.as_str()).unwrap_or(""),
230        );
231    }
232}
233
234impl<C> FormWith<C> for i32 {
235    fn new_form_with_(
236        _context: &C,
237        field: &str,
238        from: Option<&Self>,
239        _depth: usize,
240    ) -> (FormElements, Box<dyn FormState<Self>>) {
241        return FromStrFormState::new::<_, Self>(
242            field,
243            from.map(|x| x.to_string()).as_ref().map(|x| x.as_str()).unwrap_or(""),
244        );
245    }
246}
247
248impl<C> FormWith<C> for i64 {
249    fn new_form_with_(
250        _context: &C,
251        field: &str,
252        from: Option<&Self>,
253        _depth: usize,
254    ) -> (FormElements, Box<dyn FormState<Self>>) {
255        return FromStrFormState::new::<_, Self>(
256            field,
257            from.map(|x| x.to_string()).as_ref().map(|x| x.as_str()).unwrap_or(""),
258        );
259    }
260}
261
262impl<C> FormWith<C> for f32 {
263    fn new_form_with_(
264        _context: &C,
265        field: &str,
266        from: Option<&Self>,
267        _depth: usize,
268    ) -> (FormElements, Box<dyn FormState<Self>>) {
269        return FromStrFormState::new::<_, Self>(
270            field,
271            from.map(|x| x.to_string()).as_ref().map(|x| x.as_str()).unwrap_or(""),
272        );
273    }
274}
275
276impl<C> FormWith<C> for f64 {
277    fn new_form_with_(
278        _context: &C,
279        field: &str,
280        from: Option<&Self>,
281        _depth: usize,
282    ) -> (FormElements, Box<dyn FormState<Self>>) {
283        return FromStrFormState::new::<_, Self>(
284            field,
285            from.map(|x| x.to_string()).as_ref().map(|x| x.as_str()).unwrap_or(""),
286        );
287    }
288}