generated from mitchell/rust_template
helper functions and docs
This commit is contained in:
parent
9368b676d2
commit
f19d53830c
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
18
src/form.rs
18
src/form.rs
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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()}
|
||||
|
||||
Reference in New Issue
Block a user