diff --git a/src/controls/mod.rs b/src/controls/mod.rs index e996e94..9328561 100644 --- a/src/controls/mod.rs +++ b/src/controls/mod.rs @@ -1,7 +1,7 @@ use std::{fmt::Display, rc::Rc, str::FromStr}; use crate::{form::FormToolData, styles::FormStyle}; -use leptos::{Signal, View}; +use leptos::{RwSignal, Signal, View}; pub mod heading; pub mod select; @@ -15,6 +15,10 @@ 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 RenderFn: + FnOnce(&FS, RwSignal) -> (View, Option>) + 'static +{ +} // implement the traits for all valid types impl ValidationFn for T where T: Fn(&FDT) -> Result<(), String> + 'static {} @@ -23,21 +27,22 @@ 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 RenderFn for F where + F: FnOnce(&FS, RwSignal) -> (View, Option>) + 'static +{ +} +/// A trait for the data needed to render an static control. pub trait VanityControlData: 'static { + /// Builds the control, returning the [`View`] that was built. fn build_control(fs: &FS, control: ControlRenderData) -> View; } -// TODO: what if the `FS` parameter was extracted to the trait level. -// Then this would be trait object able. -// If this is trait object able, then we can store this in a list, -// and differ rendering the control until we actually need to form view. -// Which, in turn, would get rid of the Form Builder as an enum (which was -// done to avoid rendering on the server). +/// A trait for the data needed to render an interactive control. pub trait ControlData: 'static { type ReturnType: Clone; - // TODO: this should also return a getter for the data + /// Builds the control, returning the [`View`] that was built. fn build_control( fs: &FS, control: ControlRenderData, @@ -47,17 +52,20 @@ pub trait ControlData: 'static { ) -> View; } +/// The data needed to render a interactive control of type `C`. pub struct ControlRenderData { pub data: Box, pub style: Vec, } +/// The data needed to render a static control of type `C`. pub struct VanityControlBuilder { pub(crate) style_attributes: Vec, pub(crate) data: C, } impl VanityControlBuilder { + /// Creates a new [`VanityControlBuilder`] with the given [`VanityControlData`]. pub(crate) fn new(data: C) -> Self { VanityControlBuilder { data, @@ -65,6 +73,7 @@ impl VanityControlBuilder { } } + /// Builds the builder into the data needed to render the control. pub(crate) fn build(self) -> ControlRenderData { ControlRenderData { data: Box::new(self.data), @@ -73,12 +82,16 @@ impl VanityControlBuilder { } } +/// The possibilities for errors when building a control. #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] pub enum ControlBuildError { - /// The field that this control belongs to is not specified. + /// The getter field was not specified. MissingGetter, + /// The setter field was not specified. MissingSetter, + /// The parse function was not specified. MissingParseFn, + /// The unparse function was not specified. MissingUnParseFn, } impl Display for ControlBuildError { @@ -93,6 +106,7 @@ impl Display for ControlBuildError { } } +/// The data returned fomr a control's build function. pub(crate) struct BuiltControlData { pub(crate) render_data: ControlRenderData, pub(crate) getter: Rc>, @@ -102,6 +116,7 @@ pub(crate) struct BuiltControlData>>, } +/// A builder for a interactive control. pub struct ControlBuilder { pub(crate) getter: Option>>, pub(crate) setter: Option>>, @@ -113,6 +128,7 @@ pub struct ControlBuilder } impl ControlBuilder { + /// Creates a new [`ControlBuilder`] with the given [`ControlData`]. pub(crate) fn new(data: C) -> Self { ControlBuilder { data, @@ -125,6 +141,9 @@ impl ControlBuilder Result, ControlBuildError> { let getter = match self.getter { Some(getter) => getter, diff --git a/src/form.rs b/src/form.rs index be4c5f9..cb3fbcf 100644 --- a/src/form.rs +++ b/src/form.rs @@ -1,6 +1,6 @@ use crate::{ controls::{ - BuiltControlData, ControlBuilder, ControlData, FieldGetter, FieldSetter, ParseFn, + BuiltControlData, ControlBuilder, ControlData, FieldGetter, FieldSetter, ParseFn, RenderFn, UnparseFn, ValidationCb, ValidationFn, VanityControlBuilder, VanityControlData, }, styles::FormStyle, @@ -53,17 +53,17 @@ impl Form { } } - pub fn validate(&self, form_data: &FD) -> Result<(), String> { - for v in self.validations.iter() { - (*v)(form_data)?; - } - Ok(()) + /// Validates the [`ToolFormData`], returning the result + pub fn validate(&self) -> Result<(), String> { + self.fd.get_untracked().validate() } + /// Gets the view associated with this [`Form`]. pub fn view(&self) -> View { self.view.clone() } + /// Splits this [`Form`] into it's parts. pub fn to_parts(self) -> (RwSignal, Validator, View) { ( self.fd, @@ -81,66 +81,29 @@ impl IntoView for Form { } } -/// A version of the [`FormBuilder`] that contains all the data -/// needed for full building of a [`Form`]. -struct FullFormBuilder { - fd: RwSignal, - fs: FS, - validations: Vec>>, - render_fns: - Vec) -> (View, Option>) + 'static>>, - // validation_cbs: Vec>, - // views: Vec, -} - -/// The internal type for building forms -/// -/// This allows us to build either the full form -/// with views, validation and data. Or we can just -/// build the validation functions. -/// -/// This is useful in the context of a server that -/// cannot or should not render the form. You can -/// still get all the validation functions from the -/// form data. -enum FormBuilderInner { - /// For building the form with views - FullBuilder(FullFormBuilder), - /// For building only the validations for the form - ValidationBuilder { - validations: Vec>>, - }, -} - /// A builder for laying out forms. /// -/// This builder allows you to specify what component should make up the form. +/// This builder allows you to specify what components should make up the form. pub struct FormBuilder { - inner: FormBuilderInner, + /// The [`ToolFormData`] signal. + fd: RwSignal, + /// The [`FormStyle`]. + fs: FS, + /// The list of [`ValidationFn`]s. + validations: Vec>>, + /// The list of functions that will render the form. + render_fns: Vec>>, } impl FormBuilder { - /// Creates a new full builder. - fn new_full_builder(starting_data: FD, form_style: FS) -> FormBuilder { + /// Creates a new [`FormBuilder`] + fn new(starting_data: FD, form_style: FS) -> FormBuilder { let fd = create_rw_signal(starting_data); FormBuilder { - inner: FormBuilderInner::FullBuilder(FullFormBuilder { - fd, - fs: form_style, - validations: Vec::new(), - render_fns: Vec::new(), - // validation_cbs: Vec::new(), - // views: Vec::new(), - }), - } - } - - /// Creates a new builder that only collects the validation functions. - fn new_validation_builder() -> FormBuilder { - FormBuilder { - inner: FormBuilderInner::ValidationBuilder { - validations: Vec::new(), - }, + fd, + fs: form_style, + validations: Vec::new(), + render_fns: Vec::new(), } } @@ -165,18 +128,14 @@ impl FormBuilder { } fn add_vanity(&mut self, vanity_control: VanityControlBuilder) { - let builder = match &mut self.inner { - FormBuilderInner::FullBuilder(fb) => fb, - FormBuilderInner::ValidationBuilder { validations: _ } => return, - }; - let render_data = vanity_control.build(); + let render_fn = move |fs: &FS, _| { let view = VanityControlData::build_control(fs, render_data); (view, None) }; - builder.render_fns.push(Box::new(render_fn)); + self.render_fns.push(Box::new(render_fn)); } fn add_control( @@ -195,19 +154,8 @@ impl FormBuilder { Err(e) => panic!("Invalid Component: {}", e), }; - let builder = match &mut self.inner { - FormBuilderInner::FullBuilder(fb) => fb, - FormBuilderInner::ValidationBuilder { - ref mut validations, - } => { - if let Some(validation_fn) = validation_fn { - validations.push(validation_fn); - } - return; - } - }; if let Some(ref validation_fn) = validation_fn { - builder.validations.push(validation_fn.clone()); + self.validations.push(validation_fn.clone()); } let render_fn = move |fs: &FS, fd: RwSignal| { @@ -224,9 +172,7 @@ impl FormBuilder { (view, Some(cb)) }; - // builder.views.push(view); - // builder.validation_cbs.push(validation_cb); - builder.render_fns.push(Box::new(render_fn)); + self.render_fns.push(Box::new(render_fn)); } fn build_control_view( @@ -299,24 +245,19 @@ impl FormBuilder { fn build_action_form( self, action: Action>>, - ) -> Option> + ) -> Form where ServFn: DeserializeOwned + ServerFn + 'static, <>::Request as ClientReq>::FormData: From, { - let builder = match self.inner { - FormBuilderInner::FullBuilder(fb) => fb, - FormBuilderInner::ValidationBuilder { validations: _ } => return None, - }; - - let (views, validation_cbs): (Vec<_>, Vec<_>) = builder + let (views, validation_cbs): (Vec<_>, Vec<_>) = self .render_fns .into_iter() - .map(|r_fn| r_fn(&builder.fs, builder.fd)) + .map(|r_fn| r_fn(&self.fs, self.fd)) .unzip(); - let elements = builder.fs.form_frame(views.into_view()); + let elements = self.fs.form_frame(views.into_view()); let on_submit = move |ev: SubmitEvent| { let mut failed = false; @@ -336,26 +277,21 @@ impl FormBuilder { }; - Some(Form { - fd: builder.fd, - validations: builder.validations, + Form { + fd: self.fd, + validations: self.validations, view, - }) + } } - fn build_plain_form(self, url: String) -> Option> { - let builder = match self.inner { - FormBuilderInner::FullBuilder(fb) => fb, - FormBuilderInner::ValidationBuilder { validations: _ } => return None, - }; - - let (views, validation_cbs): (Vec<_>, Vec<_>) = builder + fn build_plain_form(self, url: String) -> Form { + let (views, validation_cbs): (Vec<_>, Vec<_>) = self .render_fns .into_iter() - .map(|r_fn| r_fn(&builder.fs, builder.fd)) + .map(|r_fn| r_fn(&self.fs, self.fd)) .unzip(); - let elements = builder.fs.form_frame(views.into_view()); + let elements = self.fs.form_frame(views.into_view()); let on_submit = move |ev: SubmitEvent| { let mut failed = false; @@ -375,19 +311,17 @@ impl FormBuilder { }; - Some(Form { - fd: builder.fd, - validations: builder.validations, + Form { + fd: self.fd, + validations: self.validations, view, - }) + } } fn validator(&self) -> Validator { - let validations = match &self.inner { - FormBuilderInner::FullBuilder(fb) => fb.validations.clone(), - FormBuilderInner::ValidationBuilder { validations } => validations.clone(), - }; - Validator { validations } + Validator { + validations: self.validations.clone(), + } } } @@ -409,15 +343,22 @@ pub trait FormToolData: Default + Clone + 'static { /// Constructs a [`Form`] for this [`FormToolData`] type. /// - /// The [`Form`] provides the way to render the form. + /// This renders the form as a the leptos_router + /// [`Form`](leptos_router::Form) + /// component. Call [`get_action_form`]\() to get the + /// [`ActionForm`](leptos_router::ActionForm) version. fn get_form(self, action: impl ToString, style: Self::Style) -> Form { - let builder = FormBuilder::new_full_builder(self, style); + let builder = FormBuilder::new(self, style); let builder = Self::build_form(builder); - builder - .build_plain_form(action.to_string()) - .expect("builder should be full builder") + builder.build_plain_form(action.to_string()) } + /// Constructs a [`Form`] for this [`FormToolData`] type. + /// + /// This renders the form as a the leptos_router + /// [`ActionForm`](leptos_router::ActionForm) + /// component. Call [`get_form`]\() to get the plain + /// [`Form`](leptos_router::Form) version. fn get_action_form( self, action: Action>>, @@ -428,19 +369,29 @@ pub trait FormToolData: Default + Clone + 'static { <>::Request as ClientReq>::FormData: From, { - let builder = FormBuilder::new_full_builder(self, style); + let builder = FormBuilder::new(self, style); let builder = Self::build_form(builder); - builder - .build_action_form(action) - .expect("builder should be full builder") + builder.build_action_form(action) } + /// Gets a [`Validator`] for this [`ToolFormData`]. + /// + /// This doesn't render the view, but just collects all the validation + /// Functions from building the form. That means it can be called on the + /// Server and no rendering will be done. + /// + /// However, the code to render the views are not configured out, it + /// simply doesn't run, so the view needs to compile even on the server. fn get_validator() -> Validator { - let builder = FormBuilder::new_validation_builder(); + let builder = FormBuilder::new(Self::default(), Self::Style::default()); let builder = Self::build_form(builder); builder.validator() } + /// Validates this [`FormToolData`] struct. + /// + /// This is shorthand for creating a validator with [`get_validator`]\() + /// and then calling `validator.validate(&self)`. fn validate(&self) -> Result<(), String> { let validator = Self::get_validator(); validator.validate(self)