diff --git a/src/controls/mod.rs b/src/controls/mod.rs index e042577..e996e94 100644 --- a/src/controls/mod.rs +++ b/src/controls/mod.rs @@ -1,4 +1,4 @@ -use std::{fmt::Display, rc::Rc}; +use std::{fmt::Display, rc::Rc, str::FromStr}; use crate::{form::FormToolData, styles::FormStyle}; use leptos::{Signal, View}; @@ -12,7 +12,7 @@ pub mod text_input; 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 UnparseFn: Fn(FDT) -> CR + 'static {} pub trait FieldGetter: Fn(FD) -> FDT + 'static {} pub trait FieldSetter: Fn(&mut FD, FDT) + 'static {} @@ -20,7 +20,7 @@ pub trait FieldSetter: Fn(&mut FD, FDT) + 'static {} impl ValidationFn for T where T: Fn(&FDT) -> Result<(), String> + 'static {} impl ValidationCb for T where T: Fn() -> bool + 'static {} impl ParseFn for F where F: Fn(CR) -> Result + 'static {} -impl UnparseFn for F where F: Fn(&FDT) -> CR + 'static {} +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 {} @@ -76,12 +76,18 @@ impl VanityControlBuilder { #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] pub enum ControlBuildError { /// The field that this control belongs to is not specified. - MissingField, + MissingGetter, + MissingSetter, + MissingParseFn, + MissingUnParseFn, } impl Display for ControlBuildError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let message = match self { - ControlBuildError::MissingField => "you must specify what field this control is for", + ControlBuildError::MissingGetter => "missing getter function", + ControlBuildError::MissingSetter => "missing setter function", + ControlBuildError::MissingParseFn => "missing parse function", + ControlBuildError::MissingUnParseFn => "missing unparse function", }; write!(f, "{}", message) } @@ -120,12 +126,22 @@ impl ControlBuilder Result, ControlBuildError> { - // either all 3 should be specified or none of them due to the possible `field` for `field_with` calls. - let (getter, setter, parse_fn, unparse_fn) = - match (self.getter, self.setter, self.parse_fn, self.unparse_fn) { - (Some(g), Some(s), Some(p), Some(u)) => (g, s, p, u), - _ => return Err(ControlBuildError::MissingField), - }; + let getter = match self.getter { + Some(getter) => getter, + None => return Err(ControlBuildError::MissingGetter), + }; + let setter = match self.setter { + Some(setter) => setter, + None => return Err(ControlBuildError::MissingSetter), + }; + let parse_fn = match self.parse_fn { + Some(parse_fn) => parse_fn, + None => return Err(ControlBuildError::MissingParseFn), + }; + let unparse_fn = match self.unparse_fn { + Some(unparse_fn) => unparse_fn, + None => return Err(ControlBuildError::MissingUnParseFn), + }; Ok(BuiltControlData { render_data: ControlRenderData { @@ -140,24 +156,34 @@ impl ControlBuilder and TryFrom - pub fn field_with( - mut self, - getter: impl FieldGetter, - setter: impl FieldSetter, - parse_fn: impl ParseFn, - unparse_fn: impl UnparseFn, - ) -> Self { + /// Sets the getter function. + /// + /// This function should get the field from the form data + /// for use in the form field. + /// + /// Setting this getter field is required. + pub fn getter(mut self, getter: impl FieldGetter) -> Self { self.getter = Some(Rc::new(getter)); - self.setter = Some(Rc::new(setter)); - self.parse_fn = Some(Box::new(parse_fn)); - self.unparse_fn = Some(Box::new(unparse_fn)); self } - /// Overrides the field's parse functions with the ones given. - pub fn parse_fns( + /// Sets the setter function. + /// + /// This function should get the field from the form data + /// for use in the form field. + /// + /// Setting this setter field is required. + pub fn setter(mut self, setter: impl FieldSetter) -> Self { + self.setter = Some(Rc::new(setter)); + self + } + + /// 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 + /// vice versa. + pub fn custom_parse( mut self, parse_fn: impl ParseFn, unparse_fn: impl UnparseFn, @@ -168,6 +194,15 @@ impl ControlBuilder Result<(), String> + 'static, @@ -181,3 +216,54 @@ impl ControlBuilder ControlBuilder +where + FD: FormToolData, + FS: FormStyle, + C: ControlData, + FDT: TryFrom<::ReturnType>, + ::ReturnType>>::Error: ToString, + ::ReturnType: From, +{ + /// Sets the parse functions to use the [`TryFrom`] and [`From`] traits + /// for parsing and unparsing respectively. + /// + /// 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 + /// vice versa. + pub fn default_parse(mut self) -> Self { + self.parse_fn = Some(Box::new(|control_return_value| { + FDT::try_from(control_return_value).map_err(|e| e.to_string()) + })); + self.unparse_fn = Some(Box::new(|field| { + ::ReturnType::from(field) + })); + self + } +} + +impl ControlBuilder +where + FD: FormToolData, + FS: FormStyle, + C: ControlData, + FDT: FromStr + ToString, + ::Err: ToString, +{ + /// Sets the parse functions to use the [`FromStr`] [`ToString`] and traits + /// for parsing and unparsing respectively. + /// + /// 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 + /// vice versa. + pub fn string_parse(mut self) -> Self { + self.parse_fn = Some(Box::new(|control_return_value| { + control_return_value + .parse::() + .map_err(|e| e.to_string()) + })); + self.unparse_fn = Some(Box::new(|field| field.to_string())); + self + } +} diff --git a/src/form.rs b/src/form.rs index 9e41027..2b05f48 100644 --- a/src/form.rs +++ b/src/form.rs @@ -90,6 +90,7 @@ struct FullFormBuilder { validation_cbs: Vec>, views: Vec, } + /// The internal type for building forms /// /// This allows us to build either the full form @@ -109,12 +110,15 @@ enum FormBuilderInner { }, } +/// A builder for laying out forms. +/// +/// This builder allows you to specify what component should make up the form. pub struct FormBuilder { inner: FormBuilderInner, } impl FormBuilder { - // TODO: remove the Default trait bound + /// Creates a new full builder. fn new_full_builder(starting_data: FD, form_style: FS) -> FormBuilder { let fd = create_rw_signal(starting_data); FormBuilder { @@ -128,6 +132,7 @@ impl FormBuilder { } } + /// Creates a new builder that only collects the validation functions. fn new_validation_builder() -> FormBuilder { FormBuilder { inner: FormBuilderInner::ValidationBuilder { @@ -225,9 +230,9 @@ impl FormBuilder { let fd = builder.fd; let value_getter = move || { let getter = getter.clone(); - // TODO: ideally, this should not be borrowed. If we get a clone when we call `.get` we should pass in the clone, not borrow this clone + // memoize so that updating one field doesn't cause a re-render for all fields let field = create_memo(move |_| getter(fd.get())); - unparse_fn(&field.get()) + unparse_fn(field.get()) }; let value_getter = value_getter.into_signal(); @@ -380,7 +385,7 @@ pub trait FormToolData: Default + Clone + 'static { /// data should be parsed and checked. fn build_form(fb: FormBuilder) -> FormBuilder; - /// Constructs a [`Form`] for this FormData type. + /// Constructs a [`Form`] for this [`FormToolData`] type. /// /// The [`Form`] provides the way to render the form. fn get_form(self, action: impl ToString, style: Self::Style) -> Form { @@ -413,4 +418,9 @@ pub trait FormToolData: Default + Clone + 'static { let builder = Self::build_form(builder); builder.validator() } + + fn validate(&self) -> Result<(), String> { + let validator = Self::get_validator(); + validator.validate(self) + } } diff --git a/src/styles/grid_form.rs b/src/styles/grid_form.rs index 8fd8160..20844fa 100644 --- a/src/styles/grid_form.rs +++ b/src/styles/grid_form.rs @@ -40,8 +40,15 @@ impl FormStyle for GridFormStyle { value_setter: Box::ReturnType)>, validation_state: Signal>, ) -> View { + let mut width = 1; + for style in control.style { + match style { + GridFormStylingAttributes::Width(w) => width = w, + } + } + view! { -
+