yew_bs/components/
form.rs

1use yew::prelude::*;
2use wasm_bindgen::JsCast;
3use crate::components::common::Size;
4#[derive(Properties, PartialEq)]
5pub struct FormControlProps {
6    #[prop_or("text".to_string())]
7    pub control_type: String,
8    #[prop_or_default]
9    pub placeholder: Option<AttrValue>,
10    #[prop_or_default]
11    pub value: Option<AttrValue>,
12    #[prop_or_default]
13    pub disabled: bool,
14    #[prop_or_default]
15    pub readonly: bool,
16    #[prop_or_default]
17    pub required: bool,
18    #[prop_or_default]
19    pub size: Option<Size>,
20    #[prop_or_default]
21    pub plaintext: bool,
22    #[prop_or_default]
23    pub rows: Option<u8>,
24    #[prop_or_default]
25    pub class: Option<AttrValue>,
26    #[prop_or_default]
27    pub name: Option<AttrValue>,
28    #[prop_or_default]
29    pub node_ref: NodeRef,
30    #[prop_or_default]
31    pub onchange: Option<Callback<Event>>,
32    #[prop_or_default]
33    pub oninput: Option<Callback<InputEvent>>,
34    #[prop_or_default]
35    pub onfocus: Option<Callback<FocusEvent>>,
36    #[prop_or_default]
37    pub onblur: Option<Callback<FocusEvent>>,
38}
39#[function_component(FormControl)]
40pub fn form_control(props: &FormControlProps) -> Html {
41    let mut classes = Classes::new();
42    if props.plaintext {
43        classes.push("form-control-plaintext");
44    } else {
45        classes.push("form-control");
46    }
47    if let Some(size) = &props.size {
48        let size_str = size.as_str();
49        if !size_str.is_empty() {
50            classes.push(format!("form-control-{}", size_str));
51        }
52    }
53    if let Some(class) = &props.class {
54        classes.push(class.to_string());
55    }
56    if props.control_type == "textarea" {
57        html! {
58            < textarea class = { classes } name = { props.name.clone() } placeholder = {
59            props.placeholder.clone() } value = { props.value.clone() } disabled = {
60            props.disabled } readonly = { props.readonly } required = { props.required }
61            rows = { props.rows.map(| r | r.to_string()) } ref = { props.node_ref.clone()
62            } onchange = { props.onchange.clone() } oninput = { props.oninput.clone() }
63            onfocus = { props.onfocus.clone() } onblur = { props.onblur.clone() } />
64        }
65    } else {
66        html! {
67            < input type = { props.control_type.clone() } class = { classes } name = {
68            props.name.clone() } placeholder = { props.placeholder.clone() } value = {
69            props.value.clone() } disabled = { props.disabled } readonly = { props
70            .readonly } required = { props.required } ref = { props.node_ref.clone() }
71            onchange = { props.onchange.clone() } oninput = { props.oninput.clone() }
72            onfocus = { props.onfocus.clone() } onblur = { props.onblur.clone() } />
73        }
74    }
75}
76#[derive(Properties, PartialEq)]
77pub struct FormLabelProps {
78    #[prop_or_default]
79    pub children: Children,
80    #[prop_or_default]
81    pub r#for: Option<AttrValue>,
82    #[prop_or_default]
83    pub column: bool,
84    #[prop_or_default]
85    pub size: Option<Size>,
86    #[prop_or_default]
87    pub class: Option<AttrValue>,
88}
89#[function_component(FormLabel)]
90pub fn form_label(props: &FormLabelProps) -> Html {
91    let mut classes = Classes::new();
92    if props.column {
93        classes.push("col-form-label");
94        if let Some(size) = &props.size {
95            let size_str = size.as_str();
96            if !size_str.is_empty() {
97                classes.push(format!("col-form-label-{}", size_str));
98            }
99        }
100    } else {
101        classes.push("form-label");
102    }
103    if let Some(class) = &props.class {
104        classes.push(class.to_string());
105    }
106    html! {
107        < label class = { classes } for = { props.r#for.clone() } > { for props.children
108        .iter() } </ label >
109    }
110}
111#[derive(Properties, PartialEq)]
112pub struct FormGroupProps {
113    #[prop_or_default]
114    pub children: Children,
115    #[prop_or_default]
116    pub class: Option<AttrValue>,
117    #[prop_or(true)]
118    pub margin_bottom: bool,
119}
120#[function_component(FormGroup)]
121pub fn form_group(props: &FormGroupProps) -> Html {
122    let mut classes = Classes::new();
123    if props.margin_bottom {
124        classes.push("mb-3");
125    }
126    if let Some(class) = &props.class {
127        classes.push(class.to_string());
128    }
129    html! {
130        < div class = { classes } > { for props.children.iter() } </ div >
131    }
132}
133#[derive(Properties, PartialEq)]
134pub struct FormTextProps {
135    #[prop_or_default]
136    pub children: Children,
137    #[prop_or(true)]
138    pub muted: bool,
139    #[prop_or_default]
140    pub class: Option<AttrValue>,
141}
142#[function_component(FormText)]
143pub fn form_text(props: &FormTextProps) -> Html {
144    let mut classes = Classes::new();
145    classes.push("form-text");
146    if props.muted {
147        classes.push("text-muted");
148    }
149    if let Some(class) = &props.class {
150        classes.push(class.to_string());
151    }
152    html! {
153        < div class = { classes } > { for props.children.iter() } </ div >
154    }
155}
156#[derive(Properties, PartialEq)]
157pub struct FormCheckProps {
158    #[prop_or_default]
159    pub children: Children,
160    #[prop_or_default]
161    pub disabled: bool,
162    #[prop_or_default]
163    pub inline: bool,
164    #[prop_or_default]
165    pub class: Option<AttrValue>,
166    #[prop_or_default]
167    pub onchange: Option<Callback<Event>>,
168    #[prop_or_default]
169    pub reverse: bool,
170}
171#[function_component(FormCheck)]
172pub fn form_check(props: &FormCheckProps) -> Html {
173    let mut classes = Classes::new();
174    classes.push("form-check");
175    if props.inline {
176        classes.push("form-check-inline");
177    }
178    if props.reverse {
179        classes.push("form-check-reverse");
180    }
181    if let Some(class) = &props.class {
182        classes.push(class.to_string());
183    }
184    html! {
185        < div class = { classes } > { for props.children.iter() } </ div >
186    }
187}
188#[derive(Properties, PartialEq)]
189pub struct FormCheckInputProps {
190    #[prop_or("checkbox".to_string())]
191    pub input_type: String,
192    #[prop_or_default]
193    pub id: Option<AttrValue>,
194    #[prop_or_default]
195    pub name: Option<AttrValue>,
196    #[prop_or_default]
197    pub checked: bool,
198    #[prop_or_default]
199    pub disabled: bool,
200    #[prop_or_default]
201    pub required: bool,
202    #[prop_or_default]
203    pub value: Option<AttrValue>,
204    #[prop_or_default]
205    pub class: Option<AttrValue>,
206    #[prop_or_default]
207    pub node_ref: NodeRef,
208    #[prop_or_default]
209    pub onchange: Option<Callback<Event>>,
210    #[prop_or_default]
211    pub onfocus: Option<Callback<FocusEvent>>,
212    #[prop_or_default]
213    pub onblur: Option<Callback<FocusEvent>>,
214}
215#[function_component(FormCheckInput)]
216pub fn form_check_input(props: &FormCheckInputProps) -> Html {
217    let mut classes = Classes::new();
218    classes.push("form-check-input");
219    if let Some(class) = &props.class {
220        classes.push(class.to_string());
221    }
222    html! {
223        < input type = { props.input_type.clone() } id = { props.id.clone() } name = {
224        props.name.clone() } checked = { props.checked } disabled = { props.disabled }
225        required = { props.required } value = { props.value.clone() } class = { classes }
226        ref = { props.node_ref.clone() } onchange = { props.onchange.clone() } onfocus =
227        { props.onfocus.clone() } onblur = { props.onblur.clone() } />
228    }
229}
230#[derive(Properties, PartialEq)]
231pub struct FormCheckLabelProps {
232    #[prop_or_default]
233    pub children: Children,
234    #[prop_or_default]
235    pub r#for: Option<AttrValue>,
236    #[prop_or_default]
237    pub class: Option<AttrValue>,
238}
239#[function_component(FormCheckLabel)]
240pub fn form_check_label(props: &FormCheckLabelProps) -> Html {
241    let mut classes = Classes::new();
242    classes.push("form-check-label");
243    if let Some(class) = &props.class {
244        classes.push(class.to_string());
245    }
246    html! {
247        < label class = { classes } for = { props.r#for.clone() } > { for props.children
248        .iter() } </ label >
249    }
250}
251#[derive(Properties, PartialEq)]
252pub struct FormSelectProps {
253    #[prop_or_default]
254    pub children: Children,
255    #[prop_or_default]
256    pub value: Option<AttrValue>,
257    #[prop_or_default]
258    pub disabled: bool,
259    #[prop_or_default]
260    pub required: bool,
261    #[prop_or_default]
262    pub size: Option<Size>,
263    #[prop_or_default]
264    pub large: bool,
265    #[prop_or_default]
266    pub class: Option<AttrValue>,
267    #[prop_or_default]
268    pub name: Option<AttrValue>,
269    #[prop_or_default]
270    pub node_ref: NodeRef,
271    #[prop_or_default]
272    pub onchange: Option<Callback<Event>>,
273    #[prop_or_default]
274    pub onfocus: Option<Callback<FocusEvent>>,
275    #[prop_or_default]
276    pub onblur: Option<Callback<FocusEvent>>,
277}
278#[function_component(FormSelect)]
279pub fn form_select(props: &FormSelectProps) -> Html {
280    let mut classes = Classes::new();
281    classes.push("form-select");
282    if let Some(size) = &props.size {
283        let size_str = size.as_str();
284        if !size_str.is_empty() {
285            classes.push(format!("form-select-{}", size_str));
286        }
287    }
288    if props.large {
289        classes.push("form-select-lg");
290    }
291    if let Some(class) = &props.class {
292        classes.push(class.to_string());
293    }
294    html! {
295        < select class = { classes } name = { props.name.clone() } value = { props.value
296        .clone() } disabled = { props.disabled } required = { props.required } ref = {
297        props.node_ref.clone() } onchange = { props.onchange.clone() } onfocus = { props
298        .onfocus.clone() } onblur = { props.onblur.clone() } > { for props.children
299        .iter() } </ select >
300    }
301}
302#[derive(Properties, PartialEq)]
303pub struct FormRangeProps {
304    #[prop_or(0)]
305    pub min: i32,
306    #[prop_or(100)]
307    pub max: i32,
308    #[prop_or(50)]
309    pub value: i32,
310    #[prop_or(1)]
311    pub step: i32,
312    #[prop_or_default]
313    pub disabled: bool,
314    #[prop_or_default]
315    pub class: Option<AttrValue>,
316    #[prop_or_default]
317    pub name: Option<AttrValue>,
318    #[prop_or_default]
319    pub node_ref: NodeRef,
320    #[prop_or_default]
321    pub onchange: Option<Callback<Event>>,
322    #[prop_or_default]
323    pub oninput: Option<Callback<InputEvent>>,
324}
325#[function_component(FormRange)]
326pub fn form_range(props: &FormRangeProps) -> Html {
327    let mut classes = Classes::new();
328    classes.push("form-range");
329    if let Some(class) = &props.class {
330        classes.push(class.to_string());
331    }
332    html! {
333        < input type = "range" class = { classes } name = { props.name.clone() } min = {
334        props.min.to_string() } max = { props.max.to_string() } value = { props.value
335        .to_string() } step = { props.step.to_string() } disabled = { props.disabled }
336        ref = { props.node_ref.clone() } onchange = { props.onchange.clone() } oninput =
337        { props.oninput.clone() } />
338    }
339}
340#[derive(Properties, PartialEq)]
341pub struct FormProcessProps {
342    #[prop_or_default]
343    pub children: Children,
344    #[prop_or_default]
345    pub on_submit: Option<Callback<String>>,
346    #[prop_or("POST".to_string())]
347    pub method: String,
348    #[prop_or_default]
349    pub action: Option<AttrValue>,
350    #[prop_or_default]
351    pub enctype: Option<AttrValue>,
352    #[prop_or(true)]
353    pub prevent_default: bool,
354    #[prop_or_default]
355    pub class: Option<AttrValue>,
356    #[prop_or_default]
357    pub node_ref: NodeRef,
358}
359#[function_component(FormProcess)]
360pub fn form_process(props: &FormProcessProps) -> Html {
361    let form_ref = use_node_ref();
362    let handle_submit = {
363        let form_ref = form_ref.clone();
364        let on_submit = props.on_submit.clone();
365        let prevent_default = props.prevent_default;
366        Callback::from(move |e: SubmitEvent| {
367            if prevent_default {
368                e.prevent_default();
369            }
370            if let Some(form_element) = form_ref.cast::<web_sys::HtmlFormElement>() {
371                let inputs = form_element
372                    .query_selector_all("input, select, textarea")
373                    .unwrap();
374                let mut form_json = serde_json::Map::new();
375                for i in 0..inputs.length() {
376                    if let Some(element) = inputs.get(i) {
377                        let html_element: &web_sys::HtmlElement = element
378                            .dyn_ref()
379                            .unwrap();
380                        if let Some(name) = html_element.get_attribute("name") {
381                            let value = if html_element.tag_name() == "INPUT" {
382                                if let Some(input) = element
383                                    .dyn_ref::<web_sys::HtmlInputElement>()
384                                {
385                                    match input.type_().as_str() {
386                                        "checkbox" => {
387                                            if input.checked() {
388                                                input.value()
389                                            } else {
390                                                "false".to_string()
391                                            }
392                                        }
393                                        "radio" => {
394                                            if input.checked() {
395                                                input.value()
396                                            } else {
397                                                continue;
398                                            }
399                                        }
400                                        _ => input.value(),
401                                    }
402                                } else {
403                                    continue;
404                                }
405                            } else if html_element.tag_name() == "SELECT" {
406                                if let Some(select) = element
407                                    .dyn_ref::<web_sys::HtmlSelectElement>()
408                                {
409                                    select.value()
410                                } else {
411                                    continue;
412                                }
413                            } else if html_element.tag_name() == "TEXTAREA" {
414                                if let Some(textarea) = element
415                                    .dyn_ref::<web_sys::HtmlTextAreaElement>()
416                                {
417                                    textarea.value()
418                                } else {
419                                    continue;
420                                }
421                            } else {
422                                continue;
423                            };
424                            form_json.insert(name, serde_json::Value::String(value));
425                        }
426                    }
427                }
428                let json_string = serde_json::to_string_pretty(&form_json).unwrap();
429                web_sys::console::log_1(
430                    &format!("Form submitted with data: {}", json_string).into(),
431                );
432                if let Some(on_submit) = &on_submit {
433                    on_submit.emit(json_string);
434                }
435            }
436        })
437    };
438    let mut classes = Classes::new();
439    classes.push("needs-validation");
440    if let Some(class) = &props.class {
441        classes.push(class.to_string());
442    }
443    html! {
444        < form ref = { form_ref } method = { props.method.clone() } action = { props
445        .action.clone() } enctype = { props.enctype.clone() } class = { classes }
446        onsubmit = { handle_submit } novalidate = { true } > { for props.children.iter()
447        } </ form >
448    }
449}