use crate::{controls::ValidationFn, form_builder::FormBuilder, styles::FormStyle}; use leptos::{ server_fn::{client::Client, codec::PostUrl, request::ClientReq, ServerFn}, *, }; use serde::de::DeserializeOwned; use std::rc::Rc; use web_sys::FormData; /// A type that can be used to validate the form data. /// /// This can be useful to use the same validation logic on the front /// end and backend without duplicating the logic. pub struct FormValidator { pub(crate) validations: Vec>>, } impl FormValidator { /// Validates the given form data. /// /// This runs all the validation functions for all the fields /// in the form. The first falure to occur (if any) will be returned. 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 { /// The form data signal. pub fd: RwSignal, /// The list of validations pub(crate) validations: Vec>>, pub(crate) view: View, } impl Form { /// Gets the [`FormValidator`] for this form. pub fn validator(&self) -> FormValidator { FormValidator { validations: self.validations.clone(), } } /// Validates the [`FormToolData`], returning the result pub fn validate(&self) -> Result<(), String> { let validator = self.validator(); validator.validate(&self.fd.get_untracked()) } /// 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, FormValidator, View) { ( self.fd, FormValidator { validations: self.validations, }, self.view, ) } } impl IntoView for Form { fn into_view(self) -> View { self.view() } } /// 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 FormToolData: Clone + 'static { type Style: FormStyle; type Context: 'static; /// 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 [`FormToolData`] type. /// /// This renders the form as a enhanced /// [`ActionForm`](leptos_router::ActionForm) that sends the form data /// directly by calling the server function. /// /// By doing this, we avoid doing the [`FromFormData`](leptos_router::FromFormData) /// conversion. However, to support /// [Progressive Enhancement](https://book.leptos.dev/progressive_enhancement/index.html), /// you should name the form elements to work with a plain ActionForm anyway. /// If progresssive enhancement is not important to you, you may freely /// use this version /// /// For the other ways to construct a [`Form`], see: /// - [`get_action_form`](Self::get_action_form) /// - [`get_plain_form`](Self::get_plain_form) fn get_form( self, action: Action>>, style: Self::Style, context: Self::Context, ) -> Form where ServFn: DeserializeOwned + ServerFn + 'static, <>::Request as ClientReq>::FormData: From, ServFn: From, { let builder = FormBuilder::new(context); let builder = Self::build_form(builder); builder.build_form(action, self, style) } /// Constructs a [`Form`] for this [`FormToolData`] type. /// /// This renders the form as a the leptos_router /// [`ActionForm`](leptos_router::ActionForm) /// component. /// /// For the other ways to construct a [`Form`], see: /// - [`get_form`](Self::get_form) /// - [`get_action_form`](Self::get_action_form) fn get_action_form( self, action: Action>>, style: Self::Style, context: Self::Context, ) -> Form where ServFn: DeserializeOwned + ServerFn + 'static, <>::Request as ClientReq>::FormData: From, { let builder = FormBuilder::new(context); let builder = Self::build_form(builder); builder.build_action_form(action, self, style) } /// Constructs a [`Form`] for this [`FormToolData`] type. /// /// This renders the form as a the leptos_router /// [`Form`](leptos_router::Form) /// component. /// /// For the other ways to construct a [`Form`], see: /// - [`get_form`](Self::get_form) /// - [`get_action_form`](Self::get_action_form) fn get_plain_form( self, action: impl ToString, style: Self::Style, context: Self::Context, ) -> Form { let builder = FormBuilder::new(context); let builder = Self::build_form(builder); builder.build_plain_form(action.to_string(), self, style) } /// Gets a [`FormValidator`] for this [`FormToolData`]. /// /// 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(context: Self::Context) -> FormValidator { let builder = FormBuilder::new(context); let builder = Self::build_form(builder); builder.validator() } /// Validates this [`FormToolData`] struct. /// /// This is shorthand for creating a validator with /// [`get_validator`](Self::get_validator)() /// and then calling `validator.validate(&self, context)`. fn validate(&self, context: Self::Context) -> Result<(), String> { let validator = Self::get_validator(context); validator.validate(self) } }