helper functions and docs

This commit is contained in:
Mitchell Marino 2024-06-05 09:57:20 -05:00
parent 9368b676d2
commit f19d53830c
3 changed files with 133 additions and 30 deletions

View File

@ -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<FDT>: Fn(&FDT) -> Result<(), String> + 'static {}
pub trait ValidationCb: Fn() -> bool + 'static {}
pub trait ParseFn<CR, FDT>: Fn(CR) -> Result<FDT, String> + 'static {}
pub trait UnparseFn<CR, FDT>: Fn(&FDT) -> CR + 'static {}
pub trait UnparseFn<CR, FDT>: Fn(FDT) -> CR + 'static {}
pub trait FieldGetter<FD, FDT>: Fn(FD) -> FDT + 'static {}
pub trait FieldSetter<FD, FDT>: Fn(&mut FD, FDT) + 'static {}
@ -20,7 +20,7 @@ pub trait FieldSetter<FD, FDT>: Fn(&mut FD, FDT) + 'static {}
impl<FDT, T> ValidationFn<FDT> for T where T: Fn(&FDT) -> Result<(), String> + 'static {}
impl<T> ValidationCb for T where T: Fn() -> bool + 'static {}
impl<CR, FDT, F> ParseFn<CR, FDT> for F where F: Fn(CR) -> Result<FDT, String> + 'static {}
impl<CR, FDT, F> UnparseFn<CR, FDT> for F where F: Fn(&FDT) -> CR + 'static {}
impl<CR, FDT, F> UnparseFn<CR, FDT> for F where F: Fn(FDT) -> CR + 'static {}
impl<FD, FDT, F> FieldGetter<FD, FDT> for F where F: Fn(FD) -> FDT + 'static {}
impl<FD, FDT, F> FieldSetter<FD, FDT> for F where F: Fn(&mut FD, FDT) + 'static {}
@ -76,12 +76,18 @@ impl<FS: FormStyle, C: VanityControlData> VanityControlBuilder<FS, C> {
#[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,11 +126,21 @@ impl<FD: FormToolData, FS: FormStyle, C: ControlData, FDT> ControlBuilder<FD, FS
}
pub(crate) fn build(self) -> Result<BuiltControlData<FD, FS, C, FDT>, 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 {
@ -140,24 +156,34 @@ impl<FD: FormToolData, FS: FormStyle, C: ControlData, FDT> ControlBuilder<FD, FS
})
}
// TODO: add method that automatically does the parse and unparse using
// TryInto<C::ReturnValue> and TryFrom<C::ReturnVlaue>
pub fn field_with(
mut self,
getter: impl FieldGetter<FD, FDT>,
setter: impl FieldSetter<FD, FDT>,
parse_fn: impl ParseFn<C::ReturnType, FDT>,
unparse_fn: impl UnparseFn<C::ReturnType, FDT>,
) -> 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<FD, FDT>) -> 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<FD, FDT>) -> 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<C::ReturnType, FDT>,
unparse_fn: impl UnparseFn<C::ReturnType, FDT>,
@ -168,6 +194,15 @@ impl<FD: FormToolData, FS: FormStyle, C: ControlData, FDT> ControlBuilder<FD, FS
}
/// Sets the validation function for this control
///
/// This allows you to check if the parsed value is a valid value.
///
/// You are given the entire [`FormToolData`] struct, but you should only
/// validate the field you are creating. You can use the other fields in
/// the struct as context.
///
/// Ex. You have a month and a day field in a form. You use the month
/// field to help ensure that the day is a valid day of that month.
pub fn validation_fn(
mut self,
validation_fn: impl Fn(&FD) -> Result<(), String> + 'static,
@ -181,3 +216,54 @@ impl<FD: FormToolData, FS: FormStyle, C: ControlData, FDT> ControlBuilder<FD, FS
self
}
}
impl<FD, FS, C, FDT> ControlBuilder<FD, FS, C, FDT>
where
FD: FormToolData,
FS: FormStyle,
C: ControlData,
FDT: TryFrom<<C as ControlData>::ReturnType>,
<FDT as TryFrom<<C as ControlData>::ReturnType>>::Error: ToString,
<C as ControlData>::ReturnType: From<FDT>,
{
/// 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| {
<C as ControlData>::ReturnType::from(field)
}));
self
}
}
impl<FD, FS, C, FDT> ControlBuilder<FD, FS, C, FDT>
where
FD: FormToolData,
FS: FormStyle,
C: ControlData<ReturnType = String>,
FDT: FromStr + ToString,
<FDT as FromStr>::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::<FDT>()
.map_err(|e| e.to_string())
}));
self.unparse_fn = Some(Box::new(|field| field.to_string()));
self
}
}

View File

@ -90,6 +90,7 @@ struct FullFormBuilder<FD: FormToolData, FS: FormStyle> {
validation_cbs: Vec<Box<dyn ValidationCb>>,
views: Vec<View>,
}
/// The internal type for building forms
///
/// This allows us to build either the full form
@ -109,12 +110,15 @@ enum FormBuilderInner<FD: FormToolData, FS: FormStyle> {
},
}
/// A builder for laying out forms.
///
/// This builder allows you to specify what component should make up the form.
pub struct FormBuilder<FD: FormToolData, FS: FormStyle> {
inner: FormBuilderInner<FD, FS>,
}
impl<FD: FormToolData, FS: FormStyle> FormBuilder<FD, FS> {
// TODO: remove the Default trait bound
/// Creates a new full builder.
fn new_full_builder(starting_data: FD, form_style: FS) -> FormBuilder<FD, FS> {
let fd = create_rw_signal(starting_data);
FormBuilder {
@ -128,6 +132,7 @@ impl<FD: FormToolData, FS: FormStyle> FormBuilder<FD, FS> {
}
}
/// Creates a new builder that only collects the validation functions.
fn new_validation_builder() -> FormBuilder<FD, FS> {
FormBuilder {
inner: FormBuilderInner::ValidationBuilder {
@ -225,9 +230,9 @@ impl<FD: FormToolData, FS: FormStyle> FormBuilder<FD, FS> {
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<Self, Self::Style>) -> FormBuilder<Self, Self::Style>;
/// 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<Self> {
@ -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)
}
}

View File

@ -40,8 +40,15 @@ impl FormStyle for GridFormStyle {
value_setter: Box<dyn Fn(<TextInputData as ControlData>::ReturnType)>,
validation_state: Signal<Result<(), String>>,
) -> View {
let mut width = 1;
for style in control.style {
match style {
GridFormStylingAttributes::Width(w) => width = w,
}
}
view! {
<div>
<div style:grid-column=format!("span {}", width)>
<div>
<label for={&control.data.name} class="form_label">
{control.data.label.as_ref()}