remove vanity builder and add docs

This commit is contained in:
2024-06-05 12:30:15 -05:00
parent 3621e5fb7b
commit a3311b2b63
2 changed files with 99 additions and 129 deletions
+71 -120
View File
@@ -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<FD: FormToolData> Form<FD> {
}
}
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<FD>, Validator<FD>, View) {
(
self.fd,
@@ -81,66 +81,29 @@ impl<FD: FormToolData> IntoView for Form<FD> {
}
}
/// A version of the [`FormBuilder`] that contains all the data
/// needed for full building of a [`Form`].
struct FullFormBuilder<FD: FormToolData, FS: FormStyle> {
fd: RwSignal<FD>,
fs: FS,
validations: Vec<Rc<dyn ValidationFn<FD>>>,
render_fns:
Vec<Box<dyn FnOnce(&FS, RwSignal<FD>) -> (View, Option<Box<dyn ValidationCb>>) + 'static>>,
// validation_cbs: Vec<Box<dyn ValidationCb>>,
// views: Vec<View>,
}
/// 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<FD: FormToolData, FS: FormStyle> {
/// For building the form with views
FullBuilder(FullFormBuilder<FD, FS>),
/// For building only the validations for the form
ValidationBuilder {
validations: Vec<Rc<dyn ValidationFn<FD>>>,
},
}
/// 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<FD: FormToolData, FS: FormStyle> {
inner: FormBuilderInner<FD, FS>,
/// The [`ToolFormData`] signal.
fd: RwSignal<FD>,
/// The [`FormStyle`].
fs: FS,
/// The list of [`ValidationFn`]s.
validations: Vec<Rc<dyn ValidationFn<FD>>>,
/// The list of functions that will render the form.
render_fns: Vec<Box<dyn RenderFn<FS, FD>>>,
}
impl<FD: FormToolData, FS: FormStyle> FormBuilder<FD, FS> {
/// Creates a new full builder.
fn new_full_builder(starting_data: FD, form_style: FS) -> FormBuilder<FD, FS> {
/// Creates a new [`FormBuilder`]
fn new(starting_data: FD, form_style: FS) -> FormBuilder<FD, FS> {
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<FD, FS> {
FormBuilder {
inner: FormBuilderInner::ValidationBuilder {
validations: Vec::new(),
},
fd,
fs: form_style,
validations: Vec::new(),
render_fns: Vec::new(),
}
}
@@ -165,18 +128,14 @@ impl<FD: FormToolData, FS: FormStyle> FormBuilder<FD, FS> {
}
fn add_vanity<C: VanityControlData>(&mut self, vanity_control: VanityControlBuilder<FS, C>) {
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<C: ControlData, FDT: Clone + PartialEq + 'static>(
@@ -195,19 +154,8 @@ impl<FD: FormToolData, FS: FormStyle> FormBuilder<FD, FS> {
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<FD>| {
@@ -224,9 +172,7 @@ impl<FD: FormToolData, FS: FormStyle> FormBuilder<FD, FS> {
(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<C: ControlData, FDT: Clone + PartialEq + 'static>(
@@ -299,24 +245,19 @@ impl<FD: FormToolData, FS: FormStyle> FormBuilder<FD, FS> {
fn build_action_form<ServFn>(
self,
action: Action<ServFn, Result<ServFn::Output, ServerFnError<ServFn::Error>>>,
) -> Option<Form<FD>>
) -> Form<FD>
where
ServFn: DeserializeOwned + ServerFn<InputEncoding = PostUrl> + 'static,
<<ServFn::Client as Client<ServFn::Error>>::Request as ClientReq<ServFn::Error>>::FormData:
From<FormData>,
{
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<FD: FormToolData, FS: FormStyle> FormBuilder<FD, FS> {
</ActionForm>
};
Some(Form {
fd: builder.fd,
validations: builder.validations,
Form {
fd: self.fd,
validations: self.validations,
view,
})
}
}
fn build_plain_form(self, url: String) -> Option<Form<FD>> {
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<FD> {
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<FD: FormToolData, FS: FormStyle> FormBuilder<FD, FS> {
</Form>
};
Some(Form {
fd: builder.fd,
validations: builder.validations,
Form {
fd: self.fd,
validations: self.validations,
view,
})
}
}
fn validator(&self) -> Validator<FD> {
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<Self> {
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<ServFn>(
self,
action: Action<ServFn, Result<ServFn::Output, ServerFnError<ServFn::Error>>>,
@@ -428,19 +369,29 @@ pub trait FormToolData: Default + Clone + 'static {
<<ServFn::Client as Client<ServFn::Error>>::Request as ClientReq<ServFn::Error>>::FormData:
From<FormData>,
{
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<Self> {
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)