generated from mitchell/rust_template
330 lines
10 KiB
Rust
330 lines
10 KiB
Rust
use std::rc::Rc;
|
|
|
|
use crate::{
|
|
controls::{
|
|
BuiltControlData, ControlBuilder, ControlData, FieldFn, ParseFn, UnparseFn, ValidationFn,
|
|
VanityControlBuilder, VanityControlData,
|
|
},
|
|
styles::FormStyle,
|
|
};
|
|
use leptos::{
|
|
create_rw_signal, create_signal, IntoSignal, IntoView, RwSignal, SignalGet, SignalSet,
|
|
SignalUpdate, View,
|
|
};
|
|
|
|
pub struct Validator<FD: FormData> {
|
|
validations: Vec<Rc<dyn ValidationFn<FD>>>,
|
|
}
|
|
|
|
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: RwSignal<FD>,
|
|
validations: Vec<Rc<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) -> (RwSignal<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: RwSignal<FD>,
|
|
fs: FS,
|
|
validations: Vec<Rc<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<Rc<dyn ValidationFn<FD>>>,
|
|
},
|
|
}
|
|
|
|
pub struct FormBuilder<FD: FormData, FS: FormStyle> {
|
|
inner: FormBuilderInner<FD, FS>,
|
|
}
|
|
|
|
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 = create_rw_signal(FD::default());
|
|
FormBuilder {
|
|
inner: FormBuilderInner::FullBuilder(FullFormBuilder {
|
|
fd,
|
|
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 = create_rw_signal(starting_data);
|
|
FormBuilder {
|
|
inner: FormBuilderInner::FullBuilder(FullFormBuilder {
|
|
fd,
|
|
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>) -> VanityControlBuilder<FS, C>,
|
|
) -> Self {
|
|
let vanity_builder = VanityControlBuilder::new(C::default());
|
|
let control = builder(vanity_builder);
|
|
self.add_vanity(control);
|
|
self
|
|
}
|
|
|
|
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 {
|
|
let control_builder = ControlBuilder::new(C::default());
|
|
let control = builder(control_builder);
|
|
self.add_control(control);
|
|
self
|
|
}
|
|
|
|
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 view = VanityControlData::build_control(&builder.fs, render_data);
|
|
builder.views.push(view);
|
|
}
|
|
|
|
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;
|
|
}
|
|
};
|
|
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_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
|
|
|
|
// TODO: change this to a get then set, so that if the validation fails,
|
|
// The value is not actually updated
|
|
fd.update(move |fd| {
|
|
let field = field_fn(fd);
|
|
*field = parsed;
|
|
|
|
if let Some(ref validation_fn) = validation_fn {
|
|
let validation_result = (validation_fn)(fd);
|
|
validation_signal_set.set(validation_result);
|
|
}
|
|
});
|
|
};
|
|
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> {
|
|
let validations = match self.inner {
|
|
FormBuilderInner::FullBuilder(fb) => fb.validations,
|
|
FormBuilderInner::ValidationBuilder { validations } => 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 {
|
|
type Style: FormStyle;
|
|
|
|
/// 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<Self, Self::Style>) -> FormBuilder<Self, Self::Style>;
|
|
|
|
/// Constructs a [`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().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().expect("builder should be full builder")
|
|
}
|
|
|
|
fn get_validator() -> Validator<Self> {
|
|
let builder = FormBuilder::new_validation_builder();
|
|
let builder = Self::build_form(builder);
|
|
builder.validator()
|
|
}
|
|
}
|