third round of dev

This commit is contained in:
2024-03-20 20:33:13 -05:00
parent 80a967346c
commit 857dcec00f
9 changed files with 353 additions and 146 deletions
+198 -36
View File
@@ -1,7 +1,6 @@
use crate::{
controls::{
Control, ControlBuilder, ControlData, ValidationFn, VanityControl, VanityControlBuilder,
VanityControlData,
ControlBuilder, ControlData, ValidationFn, VanityControlBuilder, VanityControlData,
},
styles::FormStyle,
};
@@ -10,31 +9,135 @@ use leptos::{
WriteSignal,
};
pub struct Form {}
pub struct Validator<FD: FormData> {
validations: Vec<Box<dyn ValidationFn<FD>>>,
}
pub struct FormBuilder<FD: FormData, FS: FormStyle> {
impl<FD: FormData> Validator<FD> {
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<FD: FormData> {
pub fd: ReadSignal<FD>,
validations: Vec<Box<dyn ValidationFn<FD>>>,
view: View,
}
impl<FD: FormData> Form<FD> {
pub fn validator(self) -> Validator<FD> {
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) -> (ReadSignal<FD>, Validator<FD>, View) {
(
self.fd,
Validator {
validations: self.validations,
},
self.view,
)
}
}
impl<FD: FormData> IntoView for Form<FD> {
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: FormData, FS: FormStyle> {
fd_get: ReadSignal<FD>,
fd_set: WriteSignal<FD>,
fs: FS,
validations: Vec<Box<ValidationFn<FD>>>,
validations: Vec<Box<dyn ValidationFn<FD>>>,
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: FormData, FS: FormStyle> {
/// For building the form with views
FullBuilder(FullFormBuilder<FD, FS>),
/// For building only the validations for the form
ValidationBuilder {
validations: Vec<Box<dyn ValidationFn<FD>>>,
},
}
pub struct FormBuilder<FD: FormData, FS: FormStyle> {
inner: FormBuilderInner<FD, FS>,
}
impl<FD: FormData, FS: FormStyle> FormBuilder<FD, FS> {
pub fn new(form_style: FS) -> FormBuilder<FD, FS> {
let (fs_get, fs_set) = create_signal(FD::default());
// TODO: remove the Default trait bound and bind it to this function only
fn new_full_builder(form_style: FS) -> FormBuilder<FD, FS> {
let (fd_get, fd_set) = create_signal(FD::default());
FormBuilder {
fd_get,
fd_set,
fs: form_style,
validations: Vec::new(),
views: Vec::new(),
inner: FormBuilderInner::FullBuilder(FullFormBuilder {
fd_get,
fd_set,
fs: form_style,
validations: Vec::new(),
views: Vec::new(),
}),
}
}
fn new_full_builder_with(starting_data: FD, form_style: FS) -> FormBuilder<FD, FS> {
let (fd_get, fd_set) = create_signal(starting_data);
FormBuilder {
inner: FormBuilderInner::FullBuilder(FullFormBuilder {
fd_get,
fd_set,
fs: form_style,
validations: Vec::new(),
views: Vec::new(),
}),
}
}
fn new_validation_builder() -> FormBuilder<FD, FS> {
FormBuilder {
inner: FormBuilderInner::ValidationBuilder {
validations: Vec::new(),
},
}
}
pub(crate) fn new_vanity<C: VanityControlData + Default>(
mut self,
builder: impl Fn(VanityControlBuilder<FS, C>) -> VanityControl<FS, C>,
builder: impl Fn(VanityControlBuilder<FS, C>) -> VanityControlBuilder<FS, C>,
) -> Self {
let vanity_builder = VanityControlBuilder::new(C::default());
let control = builder(vanity_builder);
@@ -44,7 +147,7 @@ impl<FD: FormData, FS: FormStyle> FormBuilder<FD, FS> {
pub(crate) fn new_control<C: ControlData + Default>(
mut self,
builder: impl Fn(ControlBuilder<FD, FS, C>) -> Control<FD, FS, C>,
builder: impl Fn(ControlBuilder<FD, FS, C>) -> ControlBuilder<FD, FS, C>,
) -> Self {
let control_builder = ControlBuilder::new(C::default());
let control = builder(control_builder);
@@ -52,43 +155,102 @@ impl<FD: FormData, FS: FormStyle> FormBuilder<FD, FS> {
self
}
fn add_vanity<C: VanityControlData>(&mut self, vanity_control: VanityControl<FS, C>) {
let view = VanityControlData::build_control::<FD, FS>(&self.fs, vanity_control);
self.views.push(view);
fn add_vanity<C: VanityControlData>(&mut self, vanity_control: VanityControlBuilder<FS, C>) {
let full_builder = match &mut self.inner {
FormBuilderInner::ValidationBuilder { validations: _ } => return,
FormBuilderInner::FullBuilder(full_builder) => full_builder,
};
let render_data = vanity_control.build();
let view = VanityControlData::build_control(&full_builder.fs, render_data);
full_builder.views.push(view);
}
fn add_control<C: ControlData>(&mut self, control: Control<FD, FS, C>) {
fn add_control<C: ControlData>(&mut self, control: ControlBuilder<FD, FS, C>) {
let full_builder = match &mut self.inner {
FormBuilderInner::ValidationBuilder { validations } => {
validations.push(control.validation_fn);
return;
}
FormBuilderInner::FullBuilder(full_builder) => full_builder,
};
let (render_data, parse_fn, validation_fn) = control.build();
let (validation_signal, validation_signal_set) = create_signal(Ok(()));
let (view, control_value) = ControlData::build_control(&self.fs, control);
let (view, control_value) =
ControlData::build_control(&full_builder.fs, render_data, validation_signal.into());
// TODO: add a signal that triggers on submit to refresh the validation on_submit
// TODO: we might want a way to see if this is the first time this ran, which would
// prevent the form's validation to pop up before the user typed anything in
// TODO: add validation here that run on the input changing, and writes
// it to the fd signals
let fd_setter = full_builder.fd_set;
create_effect(move |last_value| {
let control_value = control_value.get();
let mut validation_result;
self.fd_set.update(|v| {
validation_result =
(control.parse_fn)(control_value, v).and_then(|_| (*control.validation)(v));
let mut validation_result = Ok(());
fd_setter.update(|v| {
validation_result = (parse_fn)(control_value, v).and_then(|_| (validation_fn)(v));
});
// TODO: or this happened on a submit
if Some(validation_result) != last_value {
validation_signal_set.set(validation_result);
if last_value.is_some_and(|last_value| last_value != validation_result) {
validation_signal_set.set(validation_result.clone());
}
validation_result
});
self.views.push(view);
full_builder.views.push(view);
}
// TODO: this should return a Form object
// The Form should have `form_view()`, and `validate(&FD)` functions.
pub fn build(self) -> View {
self.views.into_view()
fn build(self) -> Form<FD> {
match self.inner {
FormBuilderInner::FullBuilder(full_builder) => Form {
fd: full_builder.fd_get,
validations: full_builder.validations,
view: full_builder.views.into_view(),
},
FormBuilderInner::ValidationBuilder { validations } => Form {
fd: create_signal(FD::default()).0,
validations,
view: ().into_view(),
},
}
}
fn validator(self) -> Validator<FD> {
match self.inner {
FormBuilderInner::FullBuilder(full_builder) => Validator {
validations: full_builder.validations,
},
FormBuilderInner::ValidationBuilder { 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 {
// TODO: this should return a Form Object
fn create_form() -> View;
type Style: FormStyle;
/// Defines how the form should be layed out and how the data should be parsed and validated.
///
/// 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<Self, Self::Style>) -> FormBuilder<Self, Self::Style>;
/// Gets the [`Form`] for this FormData type.
///
/// The [`Form`] provides the way to render the form
fn get_form(style: Self::Style) -> Form<Self> {
let builder = FormBuilder::new_full_builder(style);
let builder = Self::build_form(builder);
builder.build()
}
fn get_form_with_starting_data(self, style: Self::Style) -> Form<Self> {
let builder = FormBuilder::new_full_builder_with(self, style);
let builder = Self::build_form(builder);
builder.build()
}
fn get_validator() -> Validator<Self> {
let builder = FormBuilder::new_validation_builder();
let builder = Self::build_form(builder);
builder.validator()
}
}