generated from mitchell/rust_template
organized
This commit is contained in:
parent
a3311b2b63
commit
63153d76a0
@ -1,10 +1,7 @@
|
|||||||
use leptos::View;
|
use leptos::View;
|
||||||
|
|
||||||
use super::{ControlRenderData, VanityControlBuilder, VanityControlData};
|
use super::{ControlRenderData, VanityControlBuilder, VanityControlData};
|
||||||
use crate::{
|
use crate::{form::FormToolData, form_builder::FormBuilder, styles::FormStyle};
|
||||||
form::{FormBuilder, FormToolData},
|
|
||||||
styles::FormStyle,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
|
||||||
pub struct HeadingData {
|
pub struct HeadingData {
|
||||||
|
|||||||
@ -1,10 +1,7 @@
|
|||||||
use leptos::{Signal, View};
|
use leptos::{Signal, View};
|
||||||
|
|
||||||
use super::{ControlBuilder, ControlData, ControlRenderData};
|
use super::{ControlBuilder, ControlData, ControlRenderData};
|
||||||
use crate::{
|
use crate::{form::FormToolData, form_builder::FormBuilder, styles::FormStyle};
|
||||||
form::{FormBuilder, FormToolData},
|
|
||||||
styles::FormStyle,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
|
||||||
pub struct SelectData {
|
pub struct SelectData {
|
||||||
|
|||||||
@ -1,10 +1,7 @@
|
|||||||
use leptos::View;
|
use leptos::View;
|
||||||
|
|
||||||
use super::{ControlRenderData, VanityControlBuilder, VanityControlData};
|
use super::{ControlRenderData, VanityControlBuilder, VanityControlData};
|
||||||
use crate::{
|
use crate::{form::FormToolData, form_builder::FormBuilder, styles::FormStyle};
|
||||||
form::{FormBuilder, FormToolData},
|
|
||||||
styles::FormStyle,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
|
||||||
pub struct SubmitData {
|
pub struct SubmitData {
|
||||||
|
|||||||
@ -1,8 +1,5 @@
|
|||||||
use super::{ControlBuilder, ControlData, ControlRenderData};
|
use super::{ControlBuilder, ControlData, ControlRenderData};
|
||||||
use crate::{
|
use crate::{form::FormToolData, form_builder::FormBuilder, styles::FormStyle};
|
||||||
form::{FormBuilder, FormToolData},
|
|
||||||
styles::FormStyle,
|
|
||||||
};
|
|
||||||
use leptos::{Signal, View};
|
use leptos::{Signal, View};
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
|
||||||
|
|||||||
@ -1,10 +1,7 @@
|
|||||||
use leptos::{Signal, View};
|
use leptos::{Signal, View};
|
||||||
|
|
||||||
use super::{ControlBuilder, ControlData, ControlRenderData};
|
use super::{ControlBuilder, ControlData, ControlRenderData};
|
||||||
use crate::{
|
use crate::{form::FormToolData, form_builder::FormBuilder, styles::FormStyle};
|
||||||
form::{FormBuilder, FormToolData},
|
|
||||||
styles::FormStyle,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub struct TextInputData {
|
pub struct TextInputData {
|
||||||
|
|||||||
261
src/form.rs
261
src/form.rs
@ -1,25 +1,18 @@
|
|||||||
use crate::{
|
use crate::{controls::ValidationFn, form_builder::FormBuilder, styles::FormStyle};
|
||||||
controls::{
|
|
||||||
BuiltControlData, ControlBuilder, ControlData, FieldGetter, FieldSetter, ParseFn, RenderFn,
|
|
||||||
UnparseFn, ValidationCb, ValidationFn, VanityControlBuilder, VanityControlData,
|
|
||||||
},
|
|
||||||
styles::FormStyle,
|
|
||||||
};
|
|
||||||
use leptos::{
|
use leptos::{
|
||||||
server_fn::{client::Client, codec::PostUrl, request::ClientReq, ServerFn},
|
server_fn::{client::Client, codec::PostUrl, request::ClientReq, ServerFn},
|
||||||
*,
|
*,
|
||||||
};
|
};
|
||||||
use leptos_router::{ActionForm, Form};
|
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use web_sys::{FormData, SubmitEvent};
|
use web_sys::FormData;
|
||||||
|
|
||||||
/// A type that can be used to validate the form data.
|
/// A type that can be used to validate the form data.
|
||||||
///
|
///
|
||||||
/// This can be useful to use the same validation logic on the front
|
/// This can be useful to use the same validation logic on the front
|
||||||
/// end and backend without duplicating the logic.
|
/// end and backend without duplicating the logic.
|
||||||
pub struct Validator<FD> {
|
pub struct Validator<FD> {
|
||||||
validations: Vec<Rc<dyn ValidationFn<FD>>>,
|
pub(crate) validations: Vec<Rc<dyn ValidationFn<FD>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<FD: FormToolData> Validator<FD> {
|
impl<FD: FormToolData> Validator<FD> {
|
||||||
@ -41,8 +34,8 @@ impl<FD: FormToolData> Validator<FD> {
|
|||||||
/// a validator for the data.
|
/// a validator for the data.
|
||||||
pub struct Form<FD: FormToolData> {
|
pub struct Form<FD: FormToolData> {
|
||||||
pub fd: RwSignal<FD>,
|
pub fd: RwSignal<FD>,
|
||||||
validations: Vec<Rc<dyn ValidationFn<FD>>>,
|
pub(crate) validations: Vec<Rc<dyn ValidationFn<FD>>>,
|
||||||
view: View,
|
pub(crate) view: View,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<FD: FormToolData> Form<FD> {
|
impl<FD: FormToolData> Form<FD> {
|
||||||
@ -81,250 +74,6 @@ impl<FD: FormToolData> IntoView for Form<FD> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A builder for laying out forms.
|
|
||||||
///
|
|
||||||
/// This builder allows you to specify what components should make up the form.
|
|
||||||
pub struct FormBuilder<FD: FormToolData, FS: FormStyle> {
|
|
||||||
/// 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 [`FormBuilder`]
|
|
||||||
fn new(starting_data: FD, form_style: FS) -> FormBuilder<FD, FS> {
|
|
||||||
let fd = create_rw_signal(starting_data);
|
|
||||||
FormBuilder {
|
|
||||||
fd,
|
|
||||||
fs: form_style,
|
|
||||||
validations: Vec::new(),
|
|
||||||
render_fns: 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: Clone + PartialEq + '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 render_data = vanity_control.build();
|
|
||||||
|
|
||||||
let render_fn = move |fs: &FS, _| {
|
|
||||||
let view = VanityControlData::build_control(fs, render_data);
|
|
||||||
(view, None)
|
|
||||||
};
|
|
||||||
|
|
||||||
self.render_fns.push(Box::new(render_fn));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_control<C: ControlData, FDT: Clone + PartialEq + 'static>(
|
|
||||||
&mut self,
|
|
||||||
control: ControlBuilder<FD, FS, C, FDT>,
|
|
||||||
) {
|
|
||||||
let BuiltControlData {
|
|
||||||
render_data,
|
|
||||||
getter,
|
|
||||||
setter,
|
|
||||||
parse_fn,
|
|
||||||
unparse_fn,
|
|
||||||
validation_fn,
|
|
||||||
} = match control.build() {
|
|
||||||
Ok(c) => c,
|
|
||||||
Err(e) => panic!("Invalid Component: {}", e),
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(ref validation_fn) = validation_fn {
|
|
||||||
self.validations.push(validation_fn.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
let render_fn = move |fs: &FS, fd: RwSignal<FD>| {
|
|
||||||
let (view, cb) = Self::build_control_view(
|
|
||||||
fd,
|
|
||||||
fs,
|
|
||||||
getter,
|
|
||||||
setter,
|
|
||||||
unparse_fn,
|
|
||||||
parse_fn,
|
|
||||||
validation_fn,
|
|
||||||
render_data,
|
|
||||||
);
|
|
||||||
(view, Some(cb))
|
|
||||||
};
|
|
||||||
|
|
||||||
self.render_fns.push(Box::new(render_fn));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_control_view<C: ControlData, FDT: Clone + PartialEq + 'static>(
|
|
||||||
fd: RwSignal<FD>,
|
|
||||||
fs: &FS,
|
|
||||||
getter: Rc<dyn FieldGetter<FD, FDT>>,
|
|
||||||
setter: Rc<dyn FieldSetter<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, Box<dyn ValidationCb>) {
|
|
||||||
let (validation_signal, validation_signal_set) = create_signal(Ok(()));
|
|
||||||
let value_getter = move || {
|
|
||||||
let getter = getter.clone();
|
|
||||||
// memoize so that updating one field doesn't cause a re-render for all fields
|
|
||||||
let field = create_memo(move |_| getter(fd.get()));
|
|
||||||
unparse_fn(field.get())
|
|
||||||
};
|
|
||||||
let value_getter = value_getter.into_signal();
|
|
||||||
|
|
||||||
let validation_cb = move || {
|
|
||||||
let validation_fn = validation_fn.as_ref();
|
|
||||||
let validation_fn = match validation_fn {
|
|
||||||
Some(v) => v,
|
|
||||||
None => return true, // No validation function, so validation passes
|
|
||||||
};
|
|
||||||
|
|
||||||
let data = fd.get();
|
|
||||||
let validation_result = validation_fn(&data);
|
|
||||||
let succeeded = validation_result.is_ok();
|
|
||||||
validation_signal_set.set(validation_result);
|
|
||||||
succeeded
|
|
||||||
};
|
|
||||||
let validation_cb = Box::new(validation_cb);
|
|
||||||
|
|
||||||
let validation_cb2 = validation_cb.clone();
|
|
||||||
let value_setter = move |value| {
|
|
||||||
let parsed = match parse_fn(value) {
|
|
||||||
Ok(p) => {
|
|
||||||
validation_signal_set.set(Ok(()));
|
|
||||||
p
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
validation_signal_set.set(Err(e));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// parse succeeded, update value and validate
|
|
||||||
fd.update(|data| {
|
|
||||||
setter(data, parsed);
|
|
||||||
});
|
|
||||||
|
|
||||||
// run validation
|
|
||||||
(validation_cb2)();
|
|
||||||
};
|
|
||||||
let value_setter = Box::new(value_setter);
|
|
||||||
|
|
||||||
let view = C::build_control(
|
|
||||||
fs,
|
|
||||||
render_data,
|
|
||||||
value_getter,
|
|
||||||
value_setter,
|
|
||||||
validation_signal.into(),
|
|
||||||
);
|
|
||||||
(view, validation_cb)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_action_form<ServFn>(
|
|
||||||
self,
|
|
||||||
action: Action<ServFn, Result<ServFn::Output, ServerFnError<ServFn::Error>>>,
|
|
||||||
) -> Form<FD>
|
|
||||||
where
|
|
||||||
ServFn: DeserializeOwned + ServerFn<InputEncoding = PostUrl> + 'static,
|
|
||||||
<<ServFn::Client as Client<ServFn::Error>>::Request as ClientReq<ServFn::Error>>::FormData:
|
|
||||||
From<FormData>,
|
|
||||||
{
|
|
||||||
let (views, validation_cbs): (Vec<_>, Vec<_>) = self
|
|
||||||
.render_fns
|
|
||||||
.into_iter()
|
|
||||||
.map(|r_fn| r_fn(&self.fs, self.fd))
|
|
||||||
.unzip();
|
|
||||||
|
|
||||||
let elements = self.fs.form_frame(views.into_view());
|
|
||||||
|
|
||||||
let on_submit = move |ev: SubmitEvent| {
|
|
||||||
let mut failed = false;
|
|
||||||
for validation in validation_cbs.iter().flatten() {
|
|
||||||
if !validation() {
|
|
||||||
failed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if failed {
|
|
||||||
ev.prevent_default();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let view = view! {
|
|
||||||
<ActionForm action=action on:submit=on_submit>
|
|
||||||
{elements}
|
|
||||||
</ActionForm>
|
|
||||||
};
|
|
||||||
|
|
||||||
Form {
|
|
||||||
fd: self.fd,
|
|
||||||
validations: self.validations,
|
|
||||||
view,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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(&self.fs, self.fd))
|
|
||||||
.unzip();
|
|
||||||
|
|
||||||
let elements = self.fs.form_frame(views.into_view());
|
|
||||||
|
|
||||||
let on_submit = move |ev: SubmitEvent| {
|
|
||||||
let mut failed = false;
|
|
||||||
for validation in validation_cbs.iter().flatten() {
|
|
||||||
if !validation() {
|
|
||||||
failed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if failed {
|
|
||||||
ev.prevent_default();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let view = view! {
|
|
||||||
<Form action=url on:submit=on_submit>
|
|
||||||
{elements}
|
|
||||||
</Form>
|
|
||||||
};
|
|
||||||
|
|
||||||
Form {
|
|
||||||
fd: self.fd,
|
|
||||||
validations: self.validations,
|
|
||||||
view,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validator(&self) -> Validator<FD> {
|
|
||||||
Validator {
|
|
||||||
validations: self.validations.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A trait allowing a form to be built around its containing data.
|
/// 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
|
/// This trait defines a function that can be used to build all the data needed
|
||||||
|
|||||||
260
src/form_builder.rs
Normal file
260
src/form_builder.rs
Normal file
@ -0,0 +1,260 @@
|
|||||||
|
use crate::{
|
||||||
|
controls::{
|
||||||
|
BuiltControlData, ControlBuilder, ControlData, FieldGetter, FieldSetter, ParseFn, RenderFn,
|
||||||
|
UnparseFn, ValidationCb, ValidationFn, VanityControlBuilder, VanityControlData,
|
||||||
|
},
|
||||||
|
form::{Form, FormToolData, Validator},
|
||||||
|
styles::FormStyle,
|
||||||
|
};
|
||||||
|
use leptos::{
|
||||||
|
server_fn::{client::Client, codec::PostUrl, request::ClientReq, ServerFn},
|
||||||
|
*,
|
||||||
|
};
|
||||||
|
use leptos_router::{ActionForm, Form};
|
||||||
|
use serde::de::DeserializeOwned;
|
||||||
|
use std::rc::Rc;
|
||||||
|
use web_sys::{FormData, SubmitEvent};
|
||||||
|
|
||||||
|
/// A builder for laying out forms.
|
||||||
|
///
|
||||||
|
/// This builder allows you to specify what components should make up the form.
|
||||||
|
pub struct FormBuilder<FD: FormToolData, FS: FormStyle> {
|
||||||
|
/// 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 [`FormBuilder`]
|
||||||
|
pub(crate) fn new(starting_data: FD, form_style: FS) -> FormBuilder<FD, FS> {
|
||||||
|
let fd = create_rw_signal(starting_data);
|
||||||
|
FormBuilder {
|
||||||
|
fd,
|
||||||
|
fs: form_style,
|
||||||
|
validations: Vec::new(),
|
||||||
|
render_fns: 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: Clone + PartialEq + '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 render_data = vanity_control.build();
|
||||||
|
|
||||||
|
let render_fn = move |fs: &FS, _| {
|
||||||
|
let view = VanityControlData::build_control(fs, render_data);
|
||||||
|
(view, None)
|
||||||
|
};
|
||||||
|
|
||||||
|
self.render_fns.push(Box::new(render_fn));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_control<C: ControlData, FDT: Clone + PartialEq + 'static>(
|
||||||
|
&mut self,
|
||||||
|
control: ControlBuilder<FD, FS, C, FDT>,
|
||||||
|
) {
|
||||||
|
let BuiltControlData {
|
||||||
|
render_data,
|
||||||
|
getter,
|
||||||
|
setter,
|
||||||
|
parse_fn,
|
||||||
|
unparse_fn,
|
||||||
|
validation_fn,
|
||||||
|
} = match control.build() {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(e) => panic!("Invalid Component: {}", e),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(ref validation_fn) = validation_fn {
|
||||||
|
self.validations.push(validation_fn.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
let render_fn = move |fs: &FS, fd: RwSignal<FD>| {
|
||||||
|
let (view, cb) = Self::build_control_view(
|
||||||
|
fd,
|
||||||
|
fs,
|
||||||
|
getter,
|
||||||
|
setter,
|
||||||
|
unparse_fn,
|
||||||
|
parse_fn,
|
||||||
|
validation_fn,
|
||||||
|
render_data,
|
||||||
|
);
|
||||||
|
(view, Some(cb))
|
||||||
|
};
|
||||||
|
|
||||||
|
self.render_fns.push(Box::new(render_fn));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_control_view<C: ControlData, FDT: Clone + PartialEq + 'static>(
|
||||||
|
fd: RwSignal<FD>,
|
||||||
|
fs: &FS,
|
||||||
|
getter: Rc<dyn FieldGetter<FD, FDT>>,
|
||||||
|
setter: Rc<dyn FieldSetter<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, Box<dyn ValidationCb>) {
|
||||||
|
let (validation_signal, validation_signal_set) = create_signal(Ok(()));
|
||||||
|
let value_getter = move || {
|
||||||
|
let getter = getter.clone();
|
||||||
|
// memoize so that updating one field doesn't cause a re-render for all fields
|
||||||
|
let field = create_memo(move |_| getter(fd.get()));
|
||||||
|
unparse_fn(field.get())
|
||||||
|
};
|
||||||
|
let value_getter = value_getter.into_signal();
|
||||||
|
|
||||||
|
let validation_cb = move || {
|
||||||
|
let validation_fn = validation_fn.as_ref();
|
||||||
|
let validation_fn = match validation_fn {
|
||||||
|
Some(v) => v,
|
||||||
|
None => return true, // No validation function, so validation passes
|
||||||
|
};
|
||||||
|
|
||||||
|
let data = fd.get();
|
||||||
|
let validation_result = validation_fn(&data);
|
||||||
|
let succeeded = validation_result.is_ok();
|
||||||
|
validation_signal_set.set(validation_result);
|
||||||
|
succeeded
|
||||||
|
};
|
||||||
|
let validation_cb = Box::new(validation_cb);
|
||||||
|
|
||||||
|
let validation_cb2 = validation_cb.clone();
|
||||||
|
let value_setter = move |value| {
|
||||||
|
let parsed = match parse_fn(value) {
|
||||||
|
Ok(p) => {
|
||||||
|
validation_signal_set.set(Ok(()));
|
||||||
|
p
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
validation_signal_set.set(Err(e));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// parse succeeded, update value and validate
|
||||||
|
fd.update(|data| {
|
||||||
|
setter(data, parsed);
|
||||||
|
});
|
||||||
|
|
||||||
|
// run validation
|
||||||
|
(validation_cb2)();
|
||||||
|
};
|
||||||
|
let value_setter = Box::new(value_setter);
|
||||||
|
|
||||||
|
let view = C::build_control(
|
||||||
|
fs,
|
||||||
|
render_data,
|
||||||
|
value_getter,
|
||||||
|
value_setter,
|
||||||
|
validation_signal.into(),
|
||||||
|
);
|
||||||
|
(view, validation_cb)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn build_action_form<ServFn>(
|
||||||
|
self,
|
||||||
|
action: Action<ServFn, Result<ServFn::Output, ServerFnError<ServFn::Error>>>,
|
||||||
|
) -> Form<FD>
|
||||||
|
where
|
||||||
|
ServFn: DeserializeOwned + ServerFn<InputEncoding = PostUrl> + 'static,
|
||||||
|
<<ServFn::Client as Client<ServFn::Error>>::Request as ClientReq<ServFn::Error>>::FormData:
|
||||||
|
From<FormData>,
|
||||||
|
{
|
||||||
|
let (views, validation_cbs): (Vec<_>, Vec<_>) = self
|
||||||
|
.render_fns
|
||||||
|
.into_iter()
|
||||||
|
.map(|r_fn| r_fn(&self.fs, self.fd))
|
||||||
|
.unzip();
|
||||||
|
|
||||||
|
let elements = self.fs.form_frame(views.into_view());
|
||||||
|
|
||||||
|
let on_submit = move |ev: SubmitEvent| {
|
||||||
|
let mut failed = false;
|
||||||
|
for validation in validation_cbs.iter().flatten() {
|
||||||
|
if !validation() {
|
||||||
|
failed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if failed {
|
||||||
|
ev.prevent_default();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let view = view! {
|
||||||
|
<ActionForm action=action on:submit=on_submit>
|
||||||
|
{elements}
|
||||||
|
</ActionForm>
|
||||||
|
};
|
||||||
|
|
||||||
|
Form {
|
||||||
|
fd: self.fd,
|
||||||
|
validations: self.validations,
|
||||||
|
view,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) 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(&self.fs, self.fd))
|
||||||
|
.unzip();
|
||||||
|
|
||||||
|
let elements = self.fs.form_frame(views.into_view());
|
||||||
|
|
||||||
|
let on_submit = move |ev: SubmitEvent| {
|
||||||
|
let mut failed = false;
|
||||||
|
for validation in validation_cbs.iter().flatten() {
|
||||||
|
if !validation() {
|
||||||
|
failed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if failed {
|
||||||
|
ev.prevent_default();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let view = view! {
|
||||||
|
<Form action=url on:submit=on_submit>
|
||||||
|
{elements}
|
||||||
|
</Form>
|
||||||
|
};
|
||||||
|
|
||||||
|
Form {
|
||||||
|
fd: self.fd,
|
||||||
|
validations: self.validations,
|
||||||
|
view,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn validator(&self) -> Validator<FD> {
|
||||||
|
Validator {
|
||||||
|
validations: self.validations.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,3 +1,7 @@
|
|||||||
pub mod controls;
|
pub mod controls;
|
||||||
pub mod form;
|
pub mod form;
|
||||||
|
pub mod form_builder;
|
||||||
pub mod styles;
|
pub mod styles;
|
||||||
|
|
||||||
|
pub use form::{Form, FormToolData, Validator};
|
||||||
|
pub use form_builder::FormBuilder;
|
||||||
|
|||||||
Reference in New Issue
Block a user