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}