diff --git a/src/controls/hidden.rs b/src/controls/hidden.rs index 11ffcf9..babee60 100644 --- a/src/controls/hidden.rs +++ b/src/controls/hidden.rs @@ -1,29 +1,37 @@ -use super::{BuilderFn, ControlBuilder, ControlData, ControlRenderData}; +use super::{ + BuilderFn, ControlRenderData, GetterVanityControlData, VanityControlBuilder, VanityControlData, +}; use crate::{form::FormToolData, form_builder::FormBuilder, styles::FormStyle}; use leptos::{Signal, View}; #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] -pub struct HiddenData; - -impl ControlData for HiddenData { - type ReturnType = String; +pub struct HiddenData { + pub name: String, +} +impl VanityControlData for HiddenData { fn build_control( fs: &FS, control: ControlRenderData, - value_getter: Signal, - _value_setter: Box, - _validation_state: Signal>, + value_getter: Option>, ) -> View { fs.hidden(control, value_getter) } } +impl GetterVanityControlData for HiddenData {} impl FormBuilder { - pub fn hidden( + pub fn hidden( self, - builder: impl BuilderFn, FD::Context>, + builder: impl BuilderFn, FD::Context>, ) -> Self { - self.new_control(builder) + self.new_vanity(builder) + } +} + +impl VanityControlBuilder { + pub fn named(mut self, control_name: impl ToString) -> Self { + self.data.name = control_name.to_string(); + self } } diff --git a/src/controls/mod.rs b/src/controls/mod.rs index 59ecd9c..47b2b25 100644 --- a/src/controls/mod.rs +++ b/src/controls/mod.rs @@ -19,12 +19,13 @@ pub mod text_area; pub mod text_input; pub trait BuilderFn: Fn(B, &CX) -> B {} -pub trait ValidationFn: Fn(&FDT) -> Result<(), String> + 'static {} +pub trait ValidationFn: Fn(&FDT) -> Result<(), String> + 'static {} pub trait ValidationCb: Fn() -> bool + 'static {} pub trait ParseFn: Fn(CR) -> Result + 'static {} pub trait UnparseFn: Fn(FDT) -> CR + 'static {} pub trait FieldGetter: Fn(FD) -> FDT + 'static {} pub trait FieldSetter: Fn(&mut FD, FDT) + 'static {} +pub trait ShowWhenFn: Fn(Signal, Rc) -> bool + 'static {} pub trait RenderFn: FnOnce(&FS, RwSignal) -> (View, Option>) + 'static { @@ -38,6 +39,7 @@ impl ParseFn for F where F: Fn(CR) -> Result + impl UnparseFn for F where F: Fn(FDT) -> CR + 'static {} impl FieldGetter for F where F: Fn(FD) -> FDT + 'static {} impl FieldSetter for F where F: Fn(&mut FD, FDT) + 'static {} +impl ShowWhenFn for F where F: Fn(Signal, Rc) -> bool + 'static {} impl RenderFn for F where F: FnOnce(&FS, RwSignal) -> (View, Option>) + 'static { @@ -81,11 +83,13 @@ pub struct VanityControlBuilder { pub(crate) style_attributes: Vec<::StylingAttributes>, pub(crate) data: C, pub(crate) getter: Option>>, + pub(crate) show_when: Option>>, } pub(crate) struct BuiltVanityControlData { pub(crate) render_data: ControlRenderData, pub(crate) getter: Option>>, + pub(crate) show_when: Option>>, } impl VanityControlBuilder { @@ -95,6 +99,7 @@ impl VanityControlBuilder { data, style_attributes: Vec::new(), getter: None, + show_when: None, } } @@ -106,9 +111,21 @@ impl VanityControlBuilder { styles: self.style_attributes, }, getter: self.getter, + show_when: self.show_when, } } + /// Sets the function to decide when to render the control. + /// + /// Validations for components that are not shown DO NOT run. + pub fn show_when( + mut self, + when: impl Fn(Signal, Rc) -> bool + 'static, + ) -> Self { + self.show_when = Some(Box::new(when)); + self + } + /// Adds a styling attribute to this control. pub fn style(mut self, attribute: ::StylingAttributes) -> Self { self.style_attributes.push(attribute); @@ -160,6 +177,7 @@ pub(crate) struct BuiltControlData { pub(crate) parse_fn: Box>, pub(crate) unparse_fn: Box>, pub(crate) validation_fn: Option>>, + pub(crate) show_when: Option>>, } /// A builder for a interactive control. @@ -170,6 +188,7 @@ pub struct ControlBuilder { pub(crate) unparse_fn: Option>>, pub(crate) validation_fn: Option>>, pub(crate) style_attributes: Vec<::StylingAttributes>, + pub(crate) show_when: Option>>, pub data: C, } @@ -184,6 +203,7 @@ impl ControlBuilder { unparse_fn: None, validation_fn: None, style_attributes: Vec::new(), + show_when: None, } } @@ -218,9 +238,21 @@ impl ControlBuilder { parse_fn, unparse_fn, validation_fn: self.validation_fn, + show_when: self.show_when, }) } + /// Sets the function to decide when to render the control. + /// + /// Validations for components that are not shown DO NOT run. + pub fn show_when( + mut self, + when: impl Fn(Signal, Rc) -> bool + 'static, + ) -> Self { + self.show_when = Some(Box::new(when)); + self + } + /// Sets the getter function. /// /// This function should get the field from the form data @@ -243,7 +275,7 @@ impl ControlBuilder { self } - /// Sets the parse functions to the ones given + /// Sets the parse functions to the ones given. /// /// The parse and unparse functions define how to turn what the user /// types in the form into what is stored in the form data struct and diff --git a/src/form_builder.rs b/src/form_builder.rs index e2ccf47..db1c3a0 100644 --- a/src/form_builder.rs +++ b/src/form_builder.rs @@ -16,9 +16,6 @@ use serde::de::DeserializeOwned; use std::rc::Rc; use web_sys::{FormData, SubmitEvent}; -// TODO: FS, and CX may be uncessisary, as FS is the same as FD::Style -// and CX is the same as FD::Context. - /// A builder for laying out forms. /// /// This builder allows you to specify what components should make up the form. @@ -92,11 +89,24 @@ impl FormBuilder { let BuiltVanityControlData { render_data, getter, + show_when, } = vanity_control.build(); + let cx = self.cx.clone(); let render_fn = move |fs: &FD::Style, fd: RwSignal| { let value_getter = getter.map(|getter| (move || getter(fd.get())).into_signal()); let view = VanityControlData::build_control(fs, render_data, value_getter); + let view = match show_when { + Some(when) => { + let when = move || when(fd.into(), cx.clone()); + view! { + + {&view} + + } + } + None => view, + }; (view, None) }; @@ -117,8 +127,9 @@ impl FormBuilder { self.validations.push(validation_fn.clone()); } + let cx = self.cx.clone(); let render_fn = move |fs: &FD::Style, fd: RwSignal| { - let (view, cb) = Self::build_control_view(fd, fs, built_control_data); + let (view, cb) = Self::build_control_view(fd, fs, built_control_data, cx); (view, Some(cb)) }; @@ -129,6 +140,7 @@ impl FormBuilder { fd: RwSignal, fs: &FD::Style, control_data: BuiltControlData, + cx: Rc, ) -> (View, Box) { let BuiltControlData { render_data, @@ -137,6 +149,7 @@ impl FormBuilder { parse_fn, unparse_fn, validation_fn, + show_when, } = control_data; let (validation_signal, validation_signal_set) = create_signal(Ok(())); @@ -188,6 +201,17 @@ impl FormBuilder { value_setter, validation_signal.into(), ); + let view = match show_when { + Some(when) => { + let when = move || when(fd.into(), cx.clone()); + view! { + + {&view} + + } + } + None => view, + }; (view, validation_cb) } diff --git a/src/styles/grid_form.rs b/src/styles/grid_form.rs index b505db6..fef04c6 100644 --- a/src/styles/grid_form.rs +++ b/src/styles/grid_form.rs @@ -77,6 +77,13 @@ impl FormStyle for GridFormStyle { value_setter: Box::ReturnType)>, validation_state: Signal>, ) -> View { + let mut width = 1; + for style in control.styles { + match style { + GridFormStylingAttributes::Width(w) => width = w, + } + } + let options_view = control .data .options @@ -91,9 +98,13 @@ impl FormStyle for GridFormStyle { .collect_view(); view! { -
- {control.data.label} - {move || validation_state.get().err()} +
+
+ + {move || validation_state.get().err()} +
}.into_view() + let value_getter = move || value_getter.map(|g| g.get()); + view! { } + .into_view() } fn radio_buttons( diff --git a/src/styles/mod.rs b/src/styles/mod.rs index 323d56f..cb66973 100644 --- a/src/styles/mod.rs +++ b/src/styles/mod.rs @@ -28,7 +28,7 @@ pub trait FormStyle: 'static { fn hidden( &self, control: ControlRenderData, - value_getter: Signal, + value_getter: Option>, ) -> View; fn text_input( &self, diff --git a/src/validation_builder.rs b/src/validation_builder.rs index b70b254..f6a23f8 100644 --- a/src/validation_builder.rs +++ b/src/validation_builder.rs @@ -14,7 +14,7 @@ type ValidationBuilderFn = dyn Fn(&str, &T) -> Result<(), String> + 'static; /// closures, but for simple validation function this builder can be helpful /// /// Validations are run in the order that they are called in the builder. -pub struct ValidationBuilder { +pub struct ValidationBuilder { /// The name of the field, for error messages. name: String, /// The getter function for the field to validate. @@ -23,7 +23,7 @@ pub struct ValidationBuilder { functions: Vec>>, } -impl ValidationBuilder { +impl ValidationBuilder { /// Creates a new empty [`ValidationBuilder`] on the given field. pub fn for_field(field_fn: impl Fn(&FD) -> &T + 'static) -> Self { ValidationBuilder { @@ -65,7 +65,7 @@ impl ValidationBuilder { } } -impl ValidationBuilder { +impl ValidationBuilder { /// Requires the field to not be empty. pub fn required(mut self) -> Self { self.functions.push(Box::new(move |name, value| { @@ -101,6 +101,19 @@ impl ValidationBuilder { })); self } + + /// Requires the field to contain `pattern`. + pub fn contains(mut self, pattern: impl ToString) -> Self { + let pattern = pattern.to_string(); + self.functions.push(Box::new(move |name, value| { + if !value.contains(&pattern) { + Err(format!("{} must contain {}", name, &pattern)) + } else { + Ok(()) + } + })); + self + } } impl + Display + 'static> ValidationBuilder {