diff --git a/src/components/checkbox.rs b/src/components/checkbox.rs index abb980e..c099950 100644 --- a/src/components/checkbox.rs +++ b/src/components/checkbox.rs @@ -2,7 +2,7 @@ use crate::common::ValueType; use crate::components::utils::create_unique_id; use leptos::prelude::*; use std::collections::HashMap; -use crate::components::{FormValidationRegistry, InputState, ValidationState}; +use crate::components::{use_validation_context, FormValidationRegistry, InputState, ValidationState}; /// # Checkbox Component /// This component should only be used from within a [`CheckboxList`] component. @@ -160,7 +160,7 @@ pub fn CheckboxList( //#endregion //#region Form Validation - let validation_context = use_context::() + let validation_context = use_validation_context() .expect("FormValidationRegistry was not found"); let error = Signal::derive(move || { diff --git a/src/components/select.rs b/src/components/select.rs index aaa91c7..d9e5284 100644 --- a/src/components/select.rs +++ b/src/components/select.rs @@ -3,6 +3,16 @@ use leptos::ev::{Event, Targeted}; use leptos::prelude::*; use leptos::web_sys::HtmlSelectElement; use std::collections::HashMap; +use leptos::logging::{log, warn}; +use crate::components::{use_validation_context, InputState, ValidationState}; + +fn validate(required: bool, value: String, default_value: String) -> ValidationState { + if required && (value.is_empty() || value == default_value) { + ValidationState::Invalid("This input is required.".to_string()) + } else { + ValidationState::Valid + } +} /// A custom-styled select input. /// @@ -11,48 +21,110 @@ use std::collections::HashMap; #[component] pub fn Select( #[prop()] value_list: Vec, + #[prop(optional, into)] default_value: Signal, #[prop(optional)] value: RwSignal, - #[prop(into)] data_member: String, + #[prop(into)] data_member: Signal, #[prop()] data_map: RwSignal>, #[prop(optional, into)] label: String, #[prop(optional, into)] disabled: Signal, + #[prop(optional, into)] required: Signal ) -> impl IntoView { - let on_change = { - let data_member = data_member.clone(); - move |e: Targeted| { - let target_value = e.target().value(); - data_map.update(|map| { - if let Some(val) = map.get_mut(&data_member) { - *val = ValueType::String(Some(target_value)); - } else { - map.insert(data_member.clone(), ValueType::String(Some(target_value))); - } - }); + //#region Value Logic + + let default_raw_value = if default_value.get_untracked().is_empty() { + data_map.get_untracked() + .get(&data_member.get_untracked()) + .cloned() + .unwrap_or_default() + .as_string().unwrap_or_default().unwrap_or_default() + } else { + let default = default_value.get_untracked(); + if !value_list.contains(&default) { + warn!("The default value {} is not in the list of options. This may cause validation issues.", default); } + + default }; + let raw_value = RwSignal::new(default_raw_value); + + Effect::new(move || { + data_map.update(|map| { + map.insert(data_member.get(), ValueType::String(Some(raw_value.get()))); + }); + }); + + //#endregion + //#region Validation Logic + + let error = RwSignal::new( + validate(required.get_untracked(), raw_value.get_untracked(), default_value.get_untracked()) + ); + warn!("Current error state: {:?}", error.get_untracked()); + let dirty = RwSignal::new(false); + let show_errors = Signal::derive(move || dirty.get() && matches!(error.get(), ValidationState::Invalid(_))); + + let input_state = InputState::new(data_member.get(), error.clone(), dirty.clone()); + + let validation_context = use_validation_context() + .expect("Could not find FormValidationRegistry context"); + + validation_context.validators.update(|list| list.push(RwSignal::new(input_state))); + + //#endregion + //#region Event Logic + + let on_change = move |e: Targeted| { + let value = e.target().value(); + log!("Found value {}", value); + raw_value.set(value.clone()); + error.set(validate(required.get(), raw_value.get(), default_value.get())); + dirty.set(true); + data_map.update(|map| { + if let Some(val) = map.get_mut(&data_member.get()) { + *val = ValueType::String(Some(value.clone())); + } else { + map.insert(data_member.get(), ValueType::String(Some(value))); + } + }); + }; + + //#endregion + view! { -
- +
+ +
{move || { - value_list.iter().map( - move |value| { - let display = value.to_owned(); - view! { } - } - ).collect::>() + match error.get() { + ValidationState::Invalid(msg) => msg, + _ => "There is no error - should not see this message.".to_string() + } }} - - +
+
} } diff --git a/src/components/text_field.rs b/src/components/text_field.rs index 7ef089b..efb7e01 100644 --- a/src/components/text_field.rs +++ b/src/components/text_field.rs @@ -3,7 +3,7 @@ use leptos::prelude::*; use std::collections::HashMap; use leptos::html::Input; use leptos::logging::log; -use crate::components::InputState; +use crate::components::{use_validation_context, InputState}; use crate::components::validated_form::{FormValidationRegistry, ValidationState}; /// Determines the available types of text fields. @@ -68,7 +68,7 @@ pub fn OutlinedTextField( let input_ref = NodeRef::::new(); // Register this input's validation signal. - let validator_context = use_context::() + let validator_context = use_validation_context() .expect("FormValidSignal was not found"); let raw_value = RwSignal::new(data_map.get_untracked() diff --git a/src/components/validated_form.rs b/src/components/validated_form.rs index 5ae17bb..b204edc 100644 --- a/src/components/validated_form.rs +++ b/src/components/validated_form.rs @@ -56,6 +56,11 @@ pub struct FormValidationRegistry { pub validators: RwSignal } +/// Gets the [`FormValidationRegistry`] context if it exists. +pub fn use_validation_context() -> Option { + use_context::() +} + /// # Validated Form Component /// /// A simple HTML `
` wrapper that executes validation logic using a [`FormValidationRegistry`]. diff --git a/src/pages/home_page.rs b/src/pages/home_page.rs index 0fe5410..7d81f1d 100644 --- a/src/pages/home_page.rs +++ b/src/pages/home_page.rs @@ -319,9 +319,11 @@ pub fn HomePage() -> impl IntoView { .into_iter() .map(|s| s.to_owned()) .collect() + default_value="Male" disabled=elements_disabled data_member="gender" data_map=form_data + required=true />