diff options
| author | Benedikt Peetz <benedikt.peetz@b-peetz.de> | 2025-10-05 13:21:31 +0200 |
|---|---|---|
| committer | Benedikt Peetz <benedikt.peetz@b-peetz.de> | 2025-10-05 13:21:31 +0200 |
| commit | 0564611c8e77e0f5791a3f4854bb456a8717e86a (patch) | |
| tree | 5f6f65a3837c2c37f984b7f3cfa5425351ff6752 | |
| parent | feat(buy): Add the framework for the /buy page (diff) | |
| download | web-client-0564611c8e77e0f5791a3f4854bb456a8717e86a.zip | |
feat(form): Provide basic form framework
| -rw-r--r-- | src/components/buy.rs | 56 | ||||
| -rw-r--r-- | src/components/form.rs | 119 | ||||
| -rw-r--r-- | src/components/input_placeholder.rs | 11 | ||||
| -rw-r--r-- | src/components/mod.rs | 1 |
4 files changed, 155 insertions, 32 deletions
diff --git a/src/components/buy.rs b/src/components/buy.rs index 86e9952..0c294ee 100644 --- a/src/components/buy.rs +++ b/src/components/buy.rs @@ -1,38 +1,34 @@ -use leptos::{ - IntoView, component, - html::Input, - prelude::{ClassAttribute, ElementChild, Get, NodeRef, OnAttribute, Set, signal}, - view, - web_sys::SubmitEvent, -}; +use leptos::{IntoView, component, view}; +use log::info; -use crate::components::{input_placeholder::InputPlaceholder, site_header::SiteHeader}; +use crate::components::{form::Form, input_placeholder::InputPlaceholder, site_header::SiteHeader}; #[component] pub fn Buy() -> impl IntoView { - let (product_barcode, set_product_barcode) = signal(String::new()); - - let input_element: NodeRef<Input> = NodeRef::new(); - - let on_submit = move |ev: SubmitEvent| { - // stop the page from reloading! - ev.prevent_default(); - - let value = input_element - .get() - // event handlers can only fire after the view - // is mounted to the DOM, so the `NodeRef` will be `Some` - .expect("<input> to exist") - .value(); - set_product_barcode.set(value); - }; - view! { <SiteHeader logo=icondata_io::IoPricetag back_location="/" name="Buy" /> - <form class="flex flex-col contents-start m-2 g-2" on:submit=on_submit> - <InputPlaceholder input_type="number" label="Product Barcode" node_ref=input_element /> - <input type="submit" value="Submit" /> - </form> - <p>"Name is: " {product_barcode}</p> + + {Form! { + on_submit = |Inputs {product_barcode, amount}| { + info!("Got product barcode: {product_barcode} with amount: {amount}"); + } + + <Input + name=product_barcode, + signal_name_get=product_barcode_get, + signal_name_set=product_barcode_set, + rust_type=u32, + html_type="number", + label="Product Barcode" + /> + <Input + name=amount, + signal_name_get=amount_get, + signal_name_set=amount_set, + rust_type=u16, + html_type="number", + label="Amount" + /> + }} } } diff --git a/src/components/form.rs b/src/components/form.rs new file mode 100644 index 0000000..fd55897 --- /dev/null +++ b/src/components/form.rs @@ -0,0 +1,119 @@ +macro_rules! Form { + ( + on_submit = |$bound:pat_param| $on_submit:block + $( + <Input + name=$name:ident, + signal_name_get=$signal_name_get:ident, + signal_name_set=$signal_name_set:ident, + rust_type=$rust_type:ty, + html_type=$input_type:literal, + label=$label:literal $(,)* + /> + )* + ) => {{ + use leptos::{ + view, + prelude::{ + Get, + NodeRef, + ElementChild, + ClassAttribute, + OnAttribute, + signal, + Set, + Show, + }, + html::Input, + web_sys::SubmitEvent + }; + + use log::info; + + + $( + let ($signal_name_get, $signal_name_set) = signal(None); + let $name: NodeRef<Input> = NodeRef::new(); + )* + + let on_submit = move |ev: SubmitEvent| { + struct Inputs { + $( + $name: $rust_type + ),* + } + + // stop the page from reloading! + ev.prevent_default(); + + $( + let value = { + let output = $name + .get() + // event handlers can only fire after the view + // is mounted to the DOM, so the `NodeRef` will be `Some` + .expect("<input> to exist") + .value(); + + let fin: Result<$rust_type, leptos::error::Error> = output + .parse() + .map_err(Into::<leptos::error::Error>::into); + fin + }; + + let $name = match value { + Ok(ok) => { + // Reset the signal + $signal_name_set.set(None); + + ok + } , + Err(err) => { + $signal_name_set.set(Some(err)); + + // Skip running the real `on_submit` + return + } + }; + )* + + let real_on_submit = |$bound| $on_submit; + real_on_submit(Inputs { + $( + $name + ),* + }) + }; + + + view! { + <form class="flex flex-col contents-start m-2 g-2" on:submit=on_submit> + $( + <InputPlaceholder input_type=$input_type label=$label node_ref=$name /> + <Show + when=move || $signal_name_get.get().is_some() + fallback=|| () + > + <p class="ps-2 text-red-300">{move || + format!( + "Input is invalid for type {}: {}", + stringify!($rust_type), + $signal_name_get.get().expect("Was `is_some`") + ) + }</p> + </Show> + )* + + <div class="static"> + <input + type="submit" + value="Submit" + class="absolute bottom-0 right-0 h-20 w-20 rounded-lg bg-green-300 m-2" + /> + </div> + </form> + } + }}; +} + +pub(crate) use Form; diff --git a/src/components/input_placeholder.rs b/src/components/input_placeholder.rs index 92f9926..05b9509 100644 --- a/src/components/input_placeholder.rs +++ b/src/components/input_placeholder.rs @@ -1,10 +1,17 @@ +use std::sync::atomic::{AtomicU32, Ordering}; + use leptos::{ IntoView, component, html::Input, prelude::{ClassAttribute, ElementChild, GlobalAttributes, NodeRef, NodeRefAttribute}, view, }; -use uuid::Uuid; + +fn get_id() -> u32 { + static ID: AtomicU32 = AtomicU32::new(0); + + ID.fetch_add(1, Ordering::Relaxed) +} #[component] pub fn InputPlaceholder( @@ -13,7 +20,7 @@ pub fn InputPlaceholder( node_ref: NodeRef<Input>, #[prop(default = None)] initial_value: Option<String>, ) -> impl IntoView { - let id = Uuid::new_v4(); + let id = get_id(); view! { <div class="relative h-14"> diff --git a/src/components/mod.rs b/src/components/mod.rs index 55e4397..f7b8dba 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -3,6 +3,7 @@ pub mod async_fetch; pub mod container; pub mod icon_p; pub mod input_placeholder; +pub mod form; // Specific pub mod buy; |
