use crate::{ controls::{ BuilderFn, BuiltControlData, BuiltVanityControlData, ControlBuilder, ControlData, ControlRenderData, FieldSetter, ParseFn, RenderFn, ValidationCb, ValidationFn, VanityControlBuilder, VanityControlData, }, form::{Form, FormToolData, FormValidator}, styles::FormStyle, }; use leptos::{ server_fn::{client::Client, codec::PostUrl, request::ClientReq, ServerFn}, *, }; use leptos_router::{ActionForm, Form}; use serde::de::DeserializeOwned; use std::rc::Rc; use web_sys::{FormData, SubmitEvent}; /// A builder for laying out forms. /// /// This builder allows you to specify what components should make up the form. /// /// ### Type Parameters /// - `FD`: FormToolData - Your form data /// - `FS`: FormStyle - The form style you are using /// - `CX`: Context - A user defined context that you are rendering the form in pub struct FormBuilder { pub(crate) cx: Rc, /// The list of [`ValidationFn`]s. pub(crate) validations: Vec>>, /// The list of functions that will render the form. pub(crate) render_fns: Vec>>, /// The list of styling attributes applied on the form level pub(crate) styles: Vec<::StylingAttributes>, } impl FormBuilder { /// Creates a new [`FormBuilder`] pub(crate) fn new(cx: FD::Context) -> Self { FormBuilder { cx: Rc::new(cx), validations: Vec::new(), render_fns: Vec::new(), styles: Vec::new(), } } /// Creates a new [`FormBuilder`] with the given Rc'ed context, for //// building a form group. pub(crate) fn new_group(cx: Rc) -> Self { FormBuilder { cx, validations: Vec::new(), render_fns: Vec::new(), styles: Vec::new(), } } pub fn style(mut self, style: ::StylingAttributes) -> Self { self.styles.push(style); self } pub(crate) fn new_vanity( mut self, builder: impl BuilderFn, FD::Context>, ) -> Self { let vanity_builder = VanityControlBuilder::new(C::default()); let control = builder(vanity_builder, self.cx.clone()); self.add_vanity(control); self } pub(crate) fn new_control( mut self, builder: impl BuilderFn, FD::Context>, ) -> Self { let control_builder = ControlBuilder::new(C::default()); let control = builder(control_builder, self.cx.clone()); self.add_control(control); self } // TODO: test this from a user context. A user adding a custom defined component. pub fn add_vanity( &mut self, vanity_control: VanityControlBuilder, ) { let BuiltVanityControlData { render_data, getter, show_when, } = vanity_control.build(); let cx = self.cx.clone(); let render_fn = move |fs: Rc, fd: RwSignal| { let render_data = Rc::new(render_data); let value_getter = getter.map(|getter| (move || getter(fd.get())).into_signal()); let view = move || VanityControlData::build_control(&*fs, render_data.clone(), value_getter); let view = match show_when { Some(when) => { let when = move || when(fd.into(), cx.clone()); view! { {view.clone()} } } None => view(), }; (view, None) }; self.render_fns.push(Box::new(render_fn)); } // TODO: test this from a user context. A user adding a custom defined component. pub fn add_control( &mut self, control: ControlBuilder, ) { let built_control_data = match control.build() { Ok(c) => c, Err(e) => panic!("Invalid Component: {}", e), }; if let Some(validation_fn) = built_control_data.validation_fn.clone() { let validation_fn = if let Some(show_when) = built_control_data.show_when.clone() { // we want the validation function to always succeed for hidden components // thus, we need to modify the validation function let cx = self.cx.clone(); let new_validation_fn = move |fd: &FD| { let (fd_signal, _) = create_signal(fd.clone()); if !show_when(fd_signal.into(), cx.clone()) { return Ok(()); } validation_fn(fd) }; Rc::new(new_validation_fn) } else { validation_fn }; self.validations.push(validation_fn); } let cx = self.cx.clone(); let render_fn = move |fs: Rc, fd: RwSignal| { let (view, cb) = Self::build_control_view(fd, fs, built_control_data, cx); (view, Some(cb)) }; self.render_fns.push(Box::new(render_fn)); } fn build_control_view( fd: RwSignal, fs: Rc, control_data: BuiltControlData, cx: Rc, ) -> (View, Box) { let BuiltControlData { render_data, getter, setter, parse_fn, unparse_fn, validation_fn, show_when, } = control_data; let render_data = Rc::new(render_data); let (validation_signal, validation_signal_set) = create_signal(Ok(())); let validation_fn_clone = validation_fn.clone(); let value_getter = move || { let fd = fd.get(); // rerun validation if it is failing if validation_signal.get_untracked().is_err() { if let Some(ref validation_fn) = validation_fn_clone { let validation_result = validation_fn(&fd); // if validation succeeds this time, resolve the validation error if validation_result.is_ok() { validation_signal_set.set(Ok(())); } } } unparse_fn(getter(fd)) }; let value_getter = value_getter.into_signal(); let cloned_show_when = show_when.clone(); let cloned_cx = cx.clone(); let validation_cb = move || { // first check if the validation signal is an error so that we // can fail on parsing issues too if let Some(Err(_)) = validation_signal.try_get_untracked() { return false; } // validation for non-visible fields always succeeds if let Some(ref show_when) = cloned_show_when { if !show_when(fd.into(), cloned_cx.clone()) { return true; } } // run the validation function on the value now let validation_fn = match validation_fn { Some(ref v) => v, None => return true, // No validation function so validation passes }; let data = fd.get_untracked(); let validation_result = validation_fn(&data); let succeeded = validation_result.is_ok(); validation_signal_set.set(validation_result); succeeded }; let validation_cb = Box::new(validation_cb); let value_setter = Self::create_value_setter( validation_cb.clone(), validation_signal_set, parse_fn, setter, fd, ); let view = move || { C::build_control( &*fs, render_data.clone(), value_getter, value_setter.clone(), validation_signal.into(), ) }; let view = match show_when { Some(when) => { let when = move || when(fd.into(), cx.clone()); view! { {view.clone()} } } None => view(), }; (view, validation_cb) } fn create_value_setter( validation_cb: Box bool + 'static>, validation_signal_set: WriteSignal>, parse_fn: Box>, setter: Rc>, fd: RwSignal, ) -> Rc { let value_setter = move |value| { let parsed = match parse_fn(value) { Ok(p) => { validation_signal_set.set(Ok(())); p } Err(e) => { validation_signal_set.set(Err(e)); return; } }; // parse succeeded, update value and validate fd.update(|data| { setter(data, parsed); }); // run validation (validation_cb)(); }; Rc::new(value_setter) } // TODO: impl build_formless function that builds the form without adding // and form components around anything pub(crate) fn build_form( self, action: Action>>, fd: FD, fs: FD::Style, ) -> Form where ServFn: DeserializeOwned + ServerFn + 'static, <>::Request as ClientReq>::FormData: From, { let fd = create_rw_signal(fd); let fs = Rc::new(fs); let (views, validation_cbs): (Vec<_>, Vec<_>) = self .render_fns .into_iter() .map(|r_fn| r_fn(fs.clone(), fd)) .unzip(); let elements = fs.form_frame(ControlRenderData { data: views.into_view(), styles: self.styles, }); let on_submit = move |ev: SubmitEvent| { let mut failed = false; for validation in validation_cbs.iter().flatten() { if !validation() { failed = true; } } if failed { ev.prevent_default(); } }; // TODO: do enhanced form with direct calling of the server_fn let view = view! { {elements} }; Form { fd, validations: self.validations, view, } } pub(crate) fn build_action_form( self, action: Action>>, fd: FD, fs: FD::Style, ) -> Form where ServFn: DeserializeOwned + ServerFn + 'static, <>::Request as ClientReq>::FormData: From, { let fd = create_rw_signal(fd); let fs = Rc::new(fs); let (views, validation_cbs): (Vec<_>, Vec<_>) = self .render_fns .into_iter() .map(|r_fn| r_fn(fs.clone(), fd)) .unzip(); let elements = fs.form_frame(ControlRenderData { data: views.into_view(), styles: self.styles, }); let on_submit = move |ev: SubmitEvent| { let mut failed = false; for validation in validation_cbs.iter().flatten() { if !validation() { failed = true; } } if failed { ev.prevent_default(); } }; let view = view! { {elements} }; Form { fd, validations: self.validations, view, } } pub(crate) fn build_plain_form(self, url: String, fd: FD, fs: FD::Style) -> Form { let fd = create_rw_signal(fd); let fs = Rc::new(fs); let (views, validation_cbs): (Vec<_>, Vec<_>) = self .render_fns .into_iter() .map(|r_fn| r_fn(fs.clone(), fd)) .unzip(); let elements = fs.form_frame(ControlRenderData { data: views.into_view(), styles: self.styles, }); let on_submit = move |ev: SubmitEvent| { let mut failed = false; for validation in validation_cbs.iter().flatten() { if !validation() { failed = true; } } if failed { ev.prevent_default(); } }; let view = view! {
{elements}
}; Form { fd, validations: self.validations, view, } } pub(crate) fn validator(&self) -> FormValidator { FormValidator { validations: self.validations.clone(), } } }