generated from mitchell/rust_template
control flow
This commit is contained in:
parent
0339a2ee96
commit
835271cd22
@ -1,29 +1,37 @@
|
||||
use super::{BuilderFn, ControlBuilder, ControlData, ControlRenderData};
|
||||
use super::{
|
||||
BuilderFn, ControlRenderData, GetterVanityControlData, VanityControlBuilder, VanityControlData,
|
||||
};
|
||||
use crate::{form::FormToolData, form_builder::FormBuilder, styles::FormStyle};
|
||||
use leptos::{Signal, View};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
|
||||
pub struct HiddenData;
|
||||
|
||||
impl ControlData for HiddenData {
|
||||
type ReturnType = String;
|
||||
pub struct HiddenData {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
impl VanityControlData for HiddenData {
|
||||
fn build_control<FS: FormStyle>(
|
||||
fs: &FS,
|
||||
control: ControlRenderData<FS, Self>,
|
||||
value_getter: Signal<Self::ReturnType>,
|
||||
_value_setter: Box<dyn Fn(Self::ReturnType)>,
|
||||
_validation_state: Signal<Result<(), String>>,
|
||||
value_getter: Option<Signal<String>>,
|
||||
) -> View {
|
||||
fs.hidden(control, value_getter)
|
||||
}
|
||||
}
|
||||
impl GetterVanityControlData for HiddenData {}
|
||||
|
||||
impl<FD: FormToolData> FormBuilder<FD> {
|
||||
pub fn hidden<FDT: Clone + PartialEq + 'static>(
|
||||
pub fn hidden(
|
||||
self,
|
||||
builder: impl BuilderFn<ControlBuilder<FD, HiddenData, FDT>, FD::Context>,
|
||||
builder: impl BuilderFn<VanityControlBuilder<FD, HiddenData>, FD::Context>,
|
||||
) -> Self {
|
||||
self.new_control(builder)
|
||||
self.new_vanity(builder)
|
||||
}
|
||||
}
|
||||
|
||||
impl<FD: FormToolData> VanityControlBuilder<FD, HiddenData> {
|
||||
pub fn named(mut self, control_name: impl ToString) -> Self {
|
||||
self.data.name = control_name.to_string();
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,12 +19,13 @@ pub mod text_area;
|
||||
pub mod text_input;
|
||||
|
||||
pub trait BuilderFn<B, CX>: Fn(B, &CX) -> B {}
|
||||
pub trait ValidationFn<FDT>: Fn(&FDT) -> Result<(), String> + 'static {}
|
||||
pub trait ValidationFn<FDT: ?Sized>: Fn(&FDT) -> Result<(), String> + 'static {}
|
||||
pub trait ValidationCb: Fn() -> bool + 'static {}
|
||||
pub trait ParseFn<CR, FDT>: Fn(CR) -> Result<FDT, String> + 'static {}
|
||||
pub trait UnparseFn<CR, FDT>: Fn(FDT) -> CR + 'static {}
|
||||
pub trait FieldGetter<FD, FDT>: Fn(FD) -> FDT + 'static {}
|
||||
pub trait FieldSetter<FD, FDT>: Fn(&mut FD, FDT) + 'static {}
|
||||
pub trait ShowWhenFn<FD: 'static, CX>: Fn(Signal<FD>, Rc<CX>) -> bool + 'static {}
|
||||
pub trait RenderFn<FS, FD>:
|
||||
FnOnce(&FS, RwSignal<FD>) -> (View, Option<Box<dyn ValidationCb>>) + 'static
|
||||
{
|
||||
@ -38,6 +39,7 @@ impl<CR, FDT, F> ParseFn<CR, FDT> for F where F: Fn(CR) -> Result<FDT, String> +
|
||||
impl<CR, FDT, F> UnparseFn<CR, FDT> for F where F: Fn(FDT) -> CR + 'static {}
|
||||
impl<FD, FDT, F> FieldGetter<FD, FDT> for F where F: Fn(FD) -> FDT + 'static {}
|
||||
impl<FD, FDT, F> FieldSetter<FD, FDT> for F where F: Fn(&mut FD, FDT) + 'static {}
|
||||
impl<FD: 'static, CX, F> ShowWhenFn<FD, CX> for F where F: Fn(Signal<FD>, Rc<CX>) -> bool + 'static {}
|
||||
impl<FS, FD, F> RenderFn<FS, FD> for F where
|
||||
F: FnOnce(&FS, RwSignal<FD>) -> (View, Option<Box<dyn ValidationCb>>) + 'static
|
||||
{
|
||||
@ -81,11 +83,13 @@ pub struct VanityControlBuilder<FD: FormToolData, C: VanityControlData> {
|
||||
pub(crate) style_attributes: Vec<<FD::Style as FormStyle>::StylingAttributes>,
|
||||
pub(crate) data: C,
|
||||
pub(crate) getter: Option<Rc<dyn FieldGetter<FD, String>>>,
|
||||
pub(crate) show_when: Option<Box<dyn ShowWhenFn<FD, FD::Context>>>,
|
||||
}
|
||||
|
||||
pub(crate) struct BuiltVanityControlData<FD: FormToolData, C: VanityControlData> {
|
||||
pub(crate) render_data: ControlRenderData<FD::Style, C>,
|
||||
pub(crate) getter: Option<Rc<dyn FieldGetter<FD, String>>>,
|
||||
pub(crate) show_when: Option<Box<dyn ShowWhenFn<FD, FD::Context>>>,
|
||||
}
|
||||
|
||||
impl<FD: FormToolData, C: VanityControlData> VanityControlBuilder<FD, C> {
|
||||
@ -95,6 +99,7 @@ impl<FD: FormToolData, C: VanityControlData> VanityControlBuilder<FD, C> {
|
||||
data,
|
||||
style_attributes: Vec::new(),
|
||||
getter: None,
|
||||
show_when: None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -106,9 +111,21 @@ impl<FD: FormToolData, C: VanityControlData> VanityControlBuilder<FD, C> {
|
||||
styles: self.style_attributes,
|
||||
},
|
||||
getter: self.getter,
|
||||
show_when: self.show_when,
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets the function to decide when to render the control.
|
||||
///
|
||||
/// Validations for components that are not shown DO NOT run.
|
||||
pub fn show_when(
|
||||
mut self,
|
||||
when: impl Fn(Signal<FD>, Rc<FD::Context>) -> bool + 'static,
|
||||
) -> Self {
|
||||
self.show_when = Some(Box::new(when));
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds a styling attribute to this control.
|
||||
pub fn style(mut self, attribute: <FD::Style as FormStyle>::StylingAttributes) -> Self {
|
||||
self.style_attributes.push(attribute);
|
||||
@ -160,6 +177,7 @@ pub(crate) struct BuiltControlData<FD: FormToolData, C: ControlData, FDT> {
|
||||
pub(crate) parse_fn: Box<dyn ParseFn<C::ReturnType, FDT>>,
|
||||
pub(crate) unparse_fn: Box<dyn UnparseFn<C::ReturnType, FDT>>,
|
||||
pub(crate) validation_fn: Option<Rc<dyn ValidationFn<FD>>>,
|
||||
pub(crate) show_when: Option<Box<dyn ShowWhenFn<FD, FD::Context>>>,
|
||||
}
|
||||
|
||||
/// A builder for a interactive control.
|
||||
@ -170,6 +188,7 @@ pub struct ControlBuilder<FD: FormToolData, C: ControlData, FDT> {
|
||||
pub(crate) unparse_fn: Option<Box<dyn UnparseFn<C::ReturnType, FDT>>>,
|
||||
pub(crate) validation_fn: Option<Rc<dyn ValidationFn<FD>>>,
|
||||
pub(crate) style_attributes: Vec<<FD::Style as FormStyle>::StylingAttributes>,
|
||||
pub(crate) show_when: Option<Box<dyn ShowWhenFn<FD, FD::Context>>>,
|
||||
pub data: C,
|
||||
}
|
||||
|
||||
@ -184,6 +203,7 @@ impl<FD: FormToolData, C: ControlData, FDT> ControlBuilder<FD, C, FDT> {
|
||||
unparse_fn: None,
|
||||
validation_fn: None,
|
||||
style_attributes: Vec::new(),
|
||||
show_when: None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -218,9 +238,21 @@ impl<FD: FormToolData, C: ControlData, FDT> ControlBuilder<FD, C, FDT> {
|
||||
parse_fn,
|
||||
unparse_fn,
|
||||
validation_fn: self.validation_fn,
|
||||
show_when: self.show_when,
|
||||
})
|
||||
}
|
||||
|
||||
/// Sets the function to decide when to render the control.
|
||||
///
|
||||
/// Validations for components that are not shown DO NOT run.
|
||||
pub fn show_when(
|
||||
mut self,
|
||||
when: impl Fn(Signal<FD>, Rc<FD::Context>) -> bool + 'static,
|
||||
) -> Self {
|
||||
self.show_when = Some(Box::new(when));
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the getter function.
|
||||
///
|
||||
/// This function should get the field from the form data
|
||||
@ -243,7 +275,7 @@ impl<FD: FormToolData, C: ControlData, FDT> ControlBuilder<FD, C, FDT> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the parse functions to the ones given
|
||||
/// Sets the parse functions to the ones given.
|
||||
///
|
||||
/// The parse and unparse functions define how to turn what the user
|
||||
/// types in the form into what is stored in the form data struct and
|
||||
|
||||
@ -16,9 +16,6 @@ use serde::de::DeserializeOwned;
|
||||
use std::rc::Rc;
|
||||
use web_sys::{FormData, SubmitEvent};
|
||||
|
||||
// TODO: FS, and CX may be uncessisary, as FS is the same as FD::Style
|
||||
// and CX is the same as FD::Context.
|
||||
|
||||
/// A builder for laying out forms.
|
||||
///
|
||||
/// This builder allows you to specify what components should make up the form.
|
||||
@ -92,11 +89,24 @@ impl<FD: FormToolData> FormBuilder<FD> {
|
||||
let BuiltVanityControlData {
|
||||
render_data,
|
||||
getter,
|
||||
show_when,
|
||||
} = vanity_control.build();
|
||||
|
||||
let cx = self.cx.clone();
|
||||
let render_fn = move |fs: &FD::Style, fd: RwSignal<FD>| {
|
||||
let value_getter = getter.map(|getter| (move || getter(fd.get())).into_signal());
|
||||
let view = VanityControlData::build_control(fs, render_data, value_getter);
|
||||
let view = match show_when {
|
||||
Some(when) => {
|
||||
let when = move || when(fd.into(), cx.clone());
|
||||
view! {
|
||||
<Show when=when>
|
||||
{&view}
|
||||
</Show>
|
||||
}
|
||||
}
|
||||
None => view,
|
||||
};
|
||||
(view, None)
|
||||
};
|
||||
|
||||
@ -117,8 +127,9 @@ impl<FD: FormToolData> FormBuilder<FD> {
|
||||
self.validations.push(validation_fn.clone());
|
||||
}
|
||||
|
||||
let cx = self.cx.clone();
|
||||
let render_fn = move |fs: &FD::Style, fd: RwSignal<FD>| {
|
||||
let (view, cb) = Self::build_control_view(fd, fs, built_control_data);
|
||||
let (view, cb) = Self::build_control_view(fd, fs, built_control_data, cx);
|
||||
(view, Some(cb))
|
||||
};
|
||||
|
||||
@ -129,6 +140,7 @@ impl<FD: FormToolData> FormBuilder<FD> {
|
||||
fd: RwSignal<FD>,
|
||||
fs: &FD::Style,
|
||||
control_data: BuiltControlData<FD, C, FDT>,
|
||||
cx: Rc<FD::Context>,
|
||||
) -> (View, Box<dyn ValidationCb>) {
|
||||
let BuiltControlData {
|
||||
render_data,
|
||||
@ -137,6 +149,7 @@ impl<FD: FormToolData> FormBuilder<FD> {
|
||||
parse_fn,
|
||||
unparse_fn,
|
||||
validation_fn,
|
||||
show_when,
|
||||
} = control_data;
|
||||
|
||||
let (validation_signal, validation_signal_set) = create_signal(Ok(()));
|
||||
@ -188,6 +201,17 @@ impl<FD: FormToolData> FormBuilder<FD> {
|
||||
value_setter,
|
||||
validation_signal.into(),
|
||||
);
|
||||
let view = match show_when {
|
||||
Some(when) => {
|
||||
let when = move || when(fd.into(), cx.clone());
|
||||
view! {
|
||||
<Show when=when>
|
||||
{&view}
|
||||
</Show>
|
||||
}
|
||||
}
|
||||
None => view,
|
||||
};
|
||||
(view, validation_cb)
|
||||
}
|
||||
|
||||
|
||||
@ -77,6 +77,13 @@ impl FormStyle for GridFormStyle {
|
||||
value_setter: Box<dyn Fn(<SelectData as ControlData>::ReturnType)>,
|
||||
validation_state: Signal<Result<(), String>>,
|
||||
) -> View {
|
||||
let mut width = 1;
|
||||
for style in control.styles {
|
||||
match style {
|
||||
GridFormStylingAttributes::Width(w) => width = w,
|
||||
}
|
||||
}
|
||||
|
||||
let options_view = control
|
||||
.data
|
||||
.options
|
||||
@ -91,9 +98,13 @@ impl FormStyle for GridFormStyle {
|
||||
.collect_view();
|
||||
|
||||
view! {
|
||||
<div style:grid-column=format!("span {}", width)>
|
||||
<div>
|
||||
<span>{control.data.label}</span>
|
||||
{move || validation_state.get().err()}
|
||||
<label for=&control.data.name class="form_label">
|
||||
{control.data.label.as_ref()}
|
||||
</label>
|
||||
<span class="form_error">{move || validation_state.get().err()}</span>
|
||||
</div>
|
||||
<select
|
||||
id=&control.data.name
|
||||
name=control.data.name
|
||||
@ -147,10 +158,12 @@ impl FormStyle for GridFormStyle {
|
||||
|
||||
fn hidden(
|
||||
&self,
|
||||
_control: ControlRenderData<Self, HiddenData>,
|
||||
value_getter: Signal<String>,
|
||||
control: ControlRenderData<Self, HiddenData>,
|
||||
value_getter: Option<Signal<String>>,
|
||||
) -> View {
|
||||
view! { <input prop:value=value_getter style="visibility: hidden"/> }.into_view()
|
||||
let value_getter = move || value_getter.map(|g| g.get());
|
||||
view! { <input name=control.data.name prop:value=value_getter style="visibility: hidden"/> }
|
||||
.into_view()
|
||||
}
|
||||
|
||||
fn radio_buttons(
|
||||
|
||||
@ -28,7 +28,7 @@ pub trait FormStyle: 'static {
|
||||
fn hidden(
|
||||
&self,
|
||||
control: ControlRenderData<Self, HiddenData>,
|
||||
value_getter: Signal<String>,
|
||||
value_getter: Option<Signal<String>>,
|
||||
) -> View;
|
||||
fn text_input(
|
||||
&self,
|
||||
|
||||
@ -14,7 +14,7 @@ type ValidationBuilderFn<T> = dyn Fn(&str, &T) -> Result<(), String> + 'static;
|
||||
/// closures, but for simple validation function this builder can be helpful
|
||||
///
|
||||
/// Validations are run in the order that they are called in the builder.
|
||||
pub struct ValidationBuilder<FD: FormToolData, T: 'static> {
|
||||
pub struct ValidationBuilder<FD: FormToolData, T: ?Sized + 'static> {
|
||||
/// The name of the field, for error messages.
|
||||
name: String,
|
||||
/// The getter function for the field to validate.
|
||||
@ -23,7 +23,7 @@ pub struct ValidationBuilder<FD: FormToolData, T: 'static> {
|
||||
functions: Vec<Box<ValidationBuilderFn<T>>>,
|
||||
}
|
||||
|
||||
impl<FD: FormToolData, T: 'static> ValidationBuilder<FD, T> {
|
||||
impl<FD: FormToolData, T: ?Sized + 'static> ValidationBuilder<FD, T> {
|
||||
/// Creates a new empty [`ValidationBuilder`] on the given field.
|
||||
pub fn for_field(field_fn: impl Fn(&FD) -> &T + 'static) -> Self {
|
||||
ValidationBuilder {
|
||||
@ -65,7 +65,7 @@ impl<FD: FormToolData, T: 'static> ValidationBuilder<FD, T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<FD: FormToolData> ValidationBuilder<FD, String> {
|
||||
impl<FD: FormToolData> ValidationBuilder<FD, str> {
|
||||
/// Requires the field to not be empty.
|
||||
pub fn required(mut self) -> Self {
|
||||
self.functions.push(Box::new(move |name, value| {
|
||||
@ -101,6 +101,19 @@ impl<FD: FormToolData> ValidationBuilder<FD, String> {
|
||||
}));
|
||||
self
|
||||
}
|
||||
|
||||
/// Requires the field to contain `pattern`.
|
||||
pub fn contains(mut self, pattern: impl ToString) -> Self {
|
||||
let pattern = pattern.to_string();
|
||||
self.functions.push(Box::new(move |name, value| {
|
||||
if !value.contains(&pattern) {
|
||||
Err(format!("{} must contain {}", name, &pattern))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<FD: FormToolData, T: PartialOrd<T> + Display + 'static> ValidationBuilder<FD, T> {
|
||||
|
||||
Reference in New Issue
Block a user