fourth round of dev

This commit is contained in:
2024-04-19 15:48:53 -05:00
parent 6edc59ecb7
commit 40dc1d8cdc
7 changed files with 262 additions and 141 deletions
+136 -69
View File
@@ -1,16 +1,19 @@
use std::rc::Rc;
use crate::{
controls::{
ControlBuilder, ControlData, ValidationFn, VanityControlBuilder, VanityControlData,
BuiltControlData, ControlBuilder, ControlData, FieldFn, ParseFn, UnparseFn, ValidationFn,
VanityControlBuilder, VanityControlData,
},
styles::FormStyle,
};
use leptos::{
create_effect, create_signal, IntoView, ReadSignal, SignalGet, SignalSet, SignalUpdate, View,
WriteSignal,
create_rw_signal, create_signal, IntoSignal, IntoView, RwSignal, SignalGet, SignalSet,
SignalUpdate, View,
};
pub struct Validator<FD: FormData> {
validations: Vec<Box<dyn ValidationFn<FD>>>,
validations: Vec<Rc<dyn ValidationFn<FD>>>,
}
impl<FD: FormData> Validator<FD> {
@@ -27,8 +30,8 @@ impl<FD: FormData> Validator<FD> {
/// 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>>>,
pub fd: RwSignal<FD>,
validations: Vec<Rc<dyn ValidationFn<FD>>>,
view: View,
}
@@ -50,7 +53,7 @@ impl<FD: FormData> Form<FD> {
self.view.clone()
}
pub fn to_parts(self) -> (ReadSignal<FD>, Validator<FD>, View) {
pub fn to_parts(self) -> (RwSignal<FD>, Validator<FD>, View) {
(
self.fd,
Validator {
@@ -63,17 +66,16 @@ impl<FD: FormData> Form<FD> {
impl<FD: FormData> IntoView for Form<FD> {
fn into_view(self) -> 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>,
fd: RwSignal<FD>,
fs: FS,
validations: Vec<Box<dyn ValidationFn<FD>>>,
validations: Vec<Rc<dyn ValidationFn<FD>>>,
views: Vec<View>,
}
/// The internal type for building forms
@@ -91,7 +93,7 @@ enum FormBuilderInner<FD: FormData, FS: FormStyle> {
FullBuilder(FullFormBuilder<FD, FS>),
/// For building only the validations for the form
ValidationBuilder {
validations: Vec<Box<dyn ValidationFn<FD>>>,
validations: Vec<Rc<dyn ValidationFn<FD>>>,
},
}
@@ -102,11 +104,10 @@ pub struct FormBuilder<FD: FormData, FS: FormStyle> {
impl<FD: FormData, FS: FormStyle> FormBuilder<FD, FS> {
// 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());
let fd = create_rw_signal(FD::default());
FormBuilder {
inner: FormBuilderInner::FullBuilder(FullFormBuilder {
fd_get,
fd_set,
fd,
fs: form_style,
validations: Vec::new(),
views: Vec::new(),
@@ -115,11 +116,10 @@ impl<FD: FormData, FS: FormStyle> FormBuilder<FD, FS> {
}
fn new_full_builder_with(starting_data: FD, form_style: FS) -> FormBuilder<FD, FS> {
let (fd_get, fd_set) = create_signal(starting_data);
let fd = create_rw_signal(starting_data);
FormBuilder {
inner: FormBuilderInner::FullBuilder(FullFormBuilder {
fd_get,
fd_set,
fd,
fs: form_style,
validations: Vec::new(),
views: Vec::new(),
@@ -145,7 +145,7 @@ impl<FD: FormData, FS: FormStyle> FormBuilder<FD, FS> {
self
}
pub(crate) fn new_control<C: ControlData + Default, FDT>(
pub(crate) fn new_control<C: ControlData + Default, FDT: 'static>(
mut self,
builder: impl Fn(ControlBuilder<FD, FS, C, FDT>) -> ControlBuilder<FD, FS, C, FDT>,
) -> Self {
@@ -156,67 +156,134 @@ impl<FD: FormData, FS: FormStyle> FormBuilder<FD, FS> {
}
fn add_vanity<C: VanityControlData>(&mut self, vanity_control: VanityControlBuilder<FS, C>) {
let full_builder = match &mut self.inner {
let builder = match &mut self.inner {
FormBuilderInner::FullBuilder(fb) => fb,
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);
let view = VanityControlData::build_control(&builder.fs, render_data);
builder.views.push(view);
}
fn add_control<C: ControlData, FDT>(&mut self, control: ControlBuilder<FD, FS, C, FDT>) {
let full_builder = match &mut self.inner {
FormBuilderInner::ValidationBuilder { validations } => {
validations.push(control.validation_fn);
fn add_control<C: ControlData, FDT: 'static>(
&mut self,
control: ControlBuilder<FD, FS, C, FDT>,
) {
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;
}
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(&full_builder.fs, render_data, validation_signal.into());
// TODO: add a signal that triggers on submit to refresh the validation on_submit
let fd_setter = full_builder.fd_set;
create_effect(move |last_value| {
let control_value = control_value.get();
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 last_value.is_some_and(|last_value| last_value != validation_result) {
validation_signal_set.set(validation_result.clone());
}
validation_result
});
full_builder.views.push(view);
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(self) -> Form<FD> {
match self.inner {
FormBuilderInner::FullBuilder(full_builder) => Form {
fd: full_builder.fd_get,
validations: full_builder.validations,
// TODO: wrap in the style's form wrapper
view: full_builder.views.into_view(),
},
FormBuilderInner::ValidationBuilder { validations } => Form {
fd: create_signal(FD::default()).0,
validations,
view: ().into_view(),
},
}
fn build_control_view<C: ControlData, FDT: 'static>(
builder: &FullFormBuilder<FD, FS>,
field_fn: Rc<dyn FieldFn<FD, FDT>>,
unparse_fn: Box<dyn UnparseFn<<C as ControlData>::ReturnType, FDT>>,
parse_fn: Box<dyn ParseFn<<C as ControlData>::ReturnType, FDT>>,
validation_fn: Option<Rc<dyn ValidationFn<FD>>>,
render_data: crate::controls::ControlRenderData<FS, C>,
) -> 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
fd.update(move |fd| {
let field = field_fn(fd);
*field = parsed;
validation_fn
.as_ref()
.and_then(|vfn| vfn(fd).err())
.map(|e| validation_signal_set.set(Err(e)));
});
};
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<Form<FD>> {
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<FD> {
match self.inner {
FormBuilderInner::FullBuilder(full_builder) => Validator {
validations: full_builder.validations,
},
FormBuilderInner::ValidationBuilder { validations } => Validator { validations },
}
let validations = match self.inner {
FormBuilderInner::FullBuilder(fb) => fb.validations,
FormBuilderInner::ValidationBuilder { validations } => validations,
};
Validator { validations }
}
}
@@ -240,13 +307,13 @@ pub trait FormData: Default + Clone + 'static {
fn get_form(style: Self::Style) -> Form<Self> {
let builder = FormBuilder::new_full_builder(style);
let builder = Self::build_form(builder);
builder.build()
builder.build().expect("builder should be full builder")
}
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()
builder.build().expect("builder should be full builder")
}
fn get_validator() -> Validator<Self> {