use std::rc::Rc; use crate::{ controls::{ BuiltControlData, ControlBuilder, ControlData, FieldFn, ParseFn, UnparseFn, ValidationFn, VanityControlBuilder, VanityControlData, }, styles::FormStyle, }; use leptos::{ create_rw_signal, create_signal, IntoSignal, IntoView, RwSignal, SignalGet, SignalSet, SignalUpdate, View, }; pub struct Validator { validations: Vec>>, } impl Validator { pub fn validate(&self, form_data: &FD) -> Result<(), String> { for v in self.validations.iter() { (*v)(form_data)?; } Ok(()) } } /// A constructed form object. /// /// With this, you can render the form, get the form data, or get /// a validator for the data. pub struct Form { pub fd: RwSignal, validations: Vec>>, view: View, } impl Form { pub fn validator(self) -> Validator { Validator { validations: self.validations, } } pub fn validate(&self, form_data: &FD) -> Result<(), String> { for v in self.validations.iter() { (*v)(form_data)?; } Ok(()) } pub fn view(&self) -> View { self.view.clone() } pub fn to_parts(self) -> (RwSignal, Validator, View) { ( self.fd, Validator { validations: self.validations, }, self.view, ) } } impl IntoView for Form { fn into_view(self) -> View { self.view() } } /// 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>>, 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>>, }, } pub struct FormBuilder { inner: FormBuilderInner, } impl FormBuilder { // TODO: remove the Default trait bound and bind it to this function only fn new_full_builder(form_style: FS) -> FormBuilder { let fd = create_rw_signal(FD::default()); FormBuilder { inner: FormBuilderInner::FullBuilder(FullFormBuilder { fd, fs: form_style, validations: Vec::new(), views: Vec::new(), }), } } fn new_full_builder_with(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(), views: Vec::new(), }), } } fn new_validation_builder() -> FormBuilder { FormBuilder { inner: FormBuilderInner::ValidationBuilder { validations: Vec::new(), }, } } pub(crate) fn new_vanity( mut self, builder: impl Fn(VanityControlBuilder) -> VanityControlBuilder, ) -> Self { let vanity_builder = VanityControlBuilder::new(C::default()); let control = builder(vanity_builder); self.add_vanity(control); self } pub(crate) fn new_control( mut self, builder: impl Fn(ControlBuilder) -> ControlBuilder, ) -> Self { let control_builder = ControlBuilder::new(C::default()); let control = builder(control_builder); self.add_control(control); self } 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 view = VanityControlData::build_control(&builder.fs, render_data); builder.views.push(view); } fn add_control( &mut self, control: ControlBuilder, ) { let BuiltControlData { render_data, field_fn, parse_fn, unparse_fn, validation_fn, } = match control.build() { Ok(c) => c, 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()); } let view = Self::build_control_view( builder, field_fn, unparse_fn, parse_fn, validation_fn, render_data, ); builder.views.push(view); } fn build_control_view( builder: &FullFormBuilder, field_fn: Rc>, unparse_fn: Box::ReturnType, FDT>>, parse_fn: Box::ReturnType, FDT>>, validation_fn: Option>>, render_data: crate::controls::ControlRenderData, ) -> View { let (validation_signal, validation_signal_set) = create_signal(Ok(())); // TODO: The on-submit triggering a validation could be done here with a create_effect let field_fn2 = field_fn.clone(); let fd_getter = builder.fd.read_only(); let value_getter = move || { // 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 let mut fd = fd_getter.get(); let field = field_fn2(&mut fd); (unparse_fn)(field) }; let value_getter = value_getter.into_signal(); let fd = builder.fd.clone(); let value_setter = move |value| { // TODO: also do this on a submit somehow let parsed = match parse_fn(value) { Ok(p) => p, Err(e) => { validation_signal_set.set(Err(e)); return; } }; // parse succeeded, update value and validate let field_fn = field_fn.clone(); // move let validation_fn = validation_fn.clone(); // move // TODO: change this to a get then set, so that if the validation fails, // The value is not actually updated fd.update(move |fd| { let field = field_fn(fd); *field = parsed; if let Some(ref validation_fn) = validation_fn { let validation_result = (validation_fn)(fd); validation_signal_set.set(validation_result); } }); }; let value_setter = Box::new(value_setter); // TODO: add a signal that triggers on submit to refresh the validation on_submit C::build_control( &builder.fs, render_data, value_getter, value_setter, validation_signal.into(), ) } fn build(self) -> Option> { let builder = match self.inner { FormBuilderInner::FullBuilder(fb) => fb, FormBuilderInner::ValidationBuilder { validations: _ } => return None, }; let view = builder.fs.form_frame(builder.views.into_view()); Some(Form { fd: builder.fd, validations: builder.validations, view, }) } fn validator(self) -> Validator { let validations = match self.inner { FormBuilderInner::FullBuilder(fb) => fb.validations, FormBuilderInner::ValidationBuilder { validations } => validations, }; Validator { validations } } } /// A trait allowing a form to be built around its containing data. /// /// This trait defines a function that can be used to build all the data needed /// to physically lay out a form, and how that data should be parsed and validated. pub trait FormData: Default + Clone + 'static { type Style: FormStyle; /// Defines how the form should be layed out and how the data should be parsed and validated. /// /// To construct a [`From`] object, use one of the [`get_form`()] methods. /// /// Uses the given form builder to specify what fields should be present /// in the form, what properties those fields should have, and how that /// data should be parsed and checked. fn build_form(fb: FormBuilder) -> FormBuilder; /// Constructs a [`Form`] for this FormData type. /// /// The [`Form`] provides the way to render the form. fn get_form(style: Self::Style) -> Form { let builder = FormBuilder::new_full_builder(style); let builder = Self::build_form(builder); builder.build().expect("builder should be full builder") } fn get_form_with_starting_data(self, style: Self::Style) -> Form { let builder = FormBuilder::new_full_builder_with(self, style); let builder = Self::build_form(builder); builder.build().expect("builder should be full builder") } fn get_validator() -> Validator { let builder = FormBuilder::new_validation_builder(); let builder = Self::build_form(builder); builder.validator() } }