From 059f904f5da7a17f3a4996171ee4bbb84367ad8e Mon Sep 17 00:00:00 2001 From: Lucas Vas Date: Thu, 25 Sep 2025 12:31:47 -0400 Subject: [PATCH] feat: update checkbox component to use open-ended structures Commit also includes changes to other components that make unused props optional. --- src/components/checkbox.rs | 73 ++++++++++++++++------- src/components/multi_entry/multi_entry.rs | 2 + src/components/multi_entry/value_type.rs | 2 +- src/components/select.rs | 5 +- src/components/text_field.rs | 14 ++--- src/pages/home_page.rs | 21 +++++-- src/pages/provider_entry.rs | 3 + 7 files changed, 80 insertions(+), 40 deletions(-) diff --git a/src/components/checkbox.rs b/src/components/checkbox.rs index 2a3bb53..1030757 100644 --- a/src/components/checkbox.rs +++ b/src/components/checkbox.rs @@ -1,4 +1,6 @@ +use std::collections::HashMap; use leptos::prelude::*; +use crate::components::ValueType; #[component] pub fn Checkbox( @@ -42,7 +44,9 @@ pub fn Checkbox( #[component] pub fn CheckboxList( - #[prop(default = RwSignal::new(vec![]))] selected: RwSignal>, + #[prop(optional)] selected: RwSignal>, + #[prop(into)] data_member: String, + #[prop()] data_map: RwSignal>, #[prop()] items: Vec, #[prop(default = RwSignal::new(false))] disabled: RwSignal, #[prop(optional, into)] label: String, @@ -53,32 +57,57 @@ pub fn CheckboxList( {items .into_iter() .map(|item| { - let is_checked = { + let checked_signal = Signal::derive({ let item_name = item.clone(); - move || { selected.get().contains(&item_name) } + let data_member = data_member.clone(); + move || { + data_map.get() // HashMap + .get(&data_member) // Option + .unwrap_or(&ValueType::List(None)) // ValueType + .as_list() // Result>, ValueType> + .unwrap_or(Some(vec!())) // Option> + .unwrap_or(vec!()) // Vec + .contains(&ValueType::String(Some(item_name.clone()))) // bool + } + }); + + let on_change = { + let item_name = item.clone(); + let data_member = data_member.clone(); + move || { + let result = ValueType::String(Some(item_name.clone())); + data_map.update(|map| { + if let Some(value_type) = map.get_mut(&data_member) { + // Get the list from the enum + if let Some(mut list) = value_type.as_list().unwrap_or(None) { + // Check if the value exists in the list or not. Both of these branches + // directly update the value within the hash map, otherwise the change + // isn't reactive. + if let Some(index) = list.iter().position(|val| *val == result) { + // Value is already selected, deselect it. + list.remove(index); + } else { + // Value is not selected, so select it. + list.push(result); + } + *value_type = ValueType::List(Some(list)); + } else { + // Update the value at the current data_member + let temp = map.get_mut(&data_member.clone()).unwrap(); + *temp = ValueType::List(Some(vec![result])); + } + } else { + // Insert a new list into the hash map + map.insert(data_member.clone(), ValueType::List(Some(vec![result]))); + } + }); + } }; - let checked_signal = Signal::derive(is_checked); - // Check if this checkbox should be selected - + view! { diff --git a/src/components/multi_entry/value_type.rs b/src/components/multi_entry/value_type.rs index c482032..541c825 100644 --- a/src/components/multi_entry/value_type.rs +++ b/src/components/multi_entry/value_type.rs @@ -4,7 +4,7 @@ use std::fmt::Display; /// Defines a set of possible input types. They allow for the type and value of every data member /// to be stored within a single `HashMap` or `Vec`. This means that even though all the data is /// created as a single type, the values are still able to be type safe. -#[derive(Clone, Serialize, Deserialize, Debug)] +#[derive(PartialEq, Clone, Serialize, Deserialize, Debug)] pub enum ValueType { /// Indicates a `String` value. String(Option), diff --git a/src/components/select.rs b/src/components/select.rs index edb26ed..312b63c 100644 --- a/src/components/select.rs +++ b/src/components/select.rs @@ -10,14 +10,13 @@ use crate::components::ValueType; /// by the `value` prop of the component. #[component] pub fn Select( - #[prop(default = vec!["Test Value".into()])] value_list: Vec, - #[prop(default = RwSignal::new("".into()))] value: RwSignal, + #[prop()] value_list: Vec, + #[prop(optional)] value: RwSignal, #[prop(into)] data_member: String, #[prop()] data_map: RwSignal>, #[prop(optional, into)] label: String, #[prop(default = RwSignal::new(false))] disabled: RwSignal, ) -> impl IntoView { - let on_change = { let data_member = data_member.clone(); move |e: Targeted| { diff --git a/src/components/text_field.rs b/src/components/text_field.rs index 3897525..a8aff8a 100644 --- a/src/components/text_field.rs +++ b/src/components/text_field.rs @@ -8,7 +8,7 @@ pub fn OutlinedTextField( #[prop(optional, into)] placeholder: String, // #[prop()] value: Subfield, #[prop(into)] data_member: String, // should this be an RwSignal?? - #[prop(optional)] data_map: RwSignal>, + #[prop()] data_map: RwSignal>, #[prop(optional)] value: RwSignal, #[prop(default = "text".to_owned(), into)] input_type: String, #[prop(optional)] disabled: RwSignal, @@ -86,13 +86,11 @@ pub fn OutlinedTextField( disabled={disabled} placeholder={placeholder} prop:name=name - prop:value={ - data_map - .get() // HashMap - .get(&data_member) // Option - .unwrap_or(&ValueType::String(None)) // ValueType - .to_string() - } + prop:value=move || data_map + .get() // HashMap + .get(&data_member) // Option + .unwrap_or(&ValueType::String(None)) // ValueType + .to_string() on:input=on_input />
Result<(), S pub async fn log_expandable(info: ExpandableInfo) -> Result<(), ServerFnError> { console_log(format!("Was given info: {:?}", info).as_str()); // Manually specify the type here since we're not able to infer the type later. - let item: HashMap = to_item(info.clone())?; + let item: HashMap = to_item(&info)?; console_log(format!("Converted into a DynamoDB item: {:?}", item).as_str()); Ok(()) @@ -181,9 +181,7 @@ pub fn HomePage() -> impl IntoView {

- // Each OutlinedTextField could be simplified later into an enum: - // Text { data_member, label, placeholder }, i.e. - // Text { "first_name", "First Name:", "John" } + // Text { data_member, label, placeholder } impl IntoView { /> - // Each RadioList could be simplified into an enum: // Radio { data_member, label, items } impl IntoView { data_map = expandable_react.data /> + + + // Select { data_member, label, items }