Merge pull request 'Various improvements' (#26) from feature/separate_validation_builder into main

Reviewed-on: #26
This commit is contained in:
Mitchell Marino 2024-06-12 21:46:27 +00:00
commit a5fd4f85ee
10 changed files with 178 additions and 146 deletions

View File

@ -1,48 +1,67 @@
use super::{ControlRenderData, VanityControlBuilder, VanityControlData}; use super::ControlRenderData;
use crate::{form::FormToolData, form_builder::FormBuilder, styles::FormStyle}; use crate::{form::FormToolData, form_builder::FormBuilder, styles::FormStyle};
use leptos::{RwSignal, Signal, View}; use leptos::RwSignal;
use std::rc::Rc; use std::rc::Rc;
use web_sys::MouseEvent; use web_sys::MouseEvent;
type ButtonAction<FD> = dyn Fn(MouseEvent, &mut FD);
#[derive(Clone)] #[derive(Clone)]
pub struct ButtonData<FD: FormToolData> { pub struct ButtonData<FD: FormToolData> {
pub(crate) text: String, pub(crate) text: String,
pub(crate) action: Option<Rc<dyn Fn(MouseEvent, &mut FD)>>, pub(crate) action: Option<Rc<ButtonAction<FD>>>,
}
// this will need to be set before calling the build method impl<FD: FormToolData> Default for ButtonData<FD> {
pub(crate) fd_signal: RwSignal<FD>, fn default() -> Self {
ButtonData {
text: String::default(),
action: None,
} }
impl<FD: FormToolData> VanityControlData for ButtonData<FD> {
fn build_control<FS: FormStyle>(
fs: &FS,
control: ControlRenderData<FS, Self>,
_value_getter: Option<Signal<String>>,
) -> View {
fs.button(control)
} }
} }
impl<FD: FormToolData, FS: FormStyle> FormBuilder<FD, FS> { impl<FD: FormToolData, FS: FormStyle> FormBuilder<FD, FS> {
pub fn button( pub fn button(
mut self, mut self,
builder: impl Fn( builder: impl Fn(ButtonBuilder<FD, FS>) -> ButtonBuilder<FD, FS>,
VanityControlBuilder<FD, FS, ButtonData<FD>>,
) -> VanityControlBuilder<FD, FS, ButtonData<FD>>,
) -> Self { ) -> Self {
let data = ButtonData { let button_builder = ButtonBuilder::new();
text: String::default(), let control = builder(button_builder);
action: None,
fd_signal: self.fd, let render_data = ControlRenderData {
data: control.data,
styles: control.styles,
}; };
let vanity_builder = VanityControlBuilder::new(data);
let control = builder(vanity_builder); let render_fn = move |fs: &FS, fd: RwSignal<FD>| {
self.add_vanity(control); let view = fs.button(render_data, fd);
(view, None)
};
self.render_fns.push(Box::new(render_fn));
self self
} }
} }
impl<FD: FormToolData, FS: FormStyle> VanityControlBuilder<FD, FS, ButtonData<FD>> { #[derive(Clone)]
pub struct ButtonBuilder<FD: FormToolData, FS: FormStyle> {
pub(crate) styles: Vec<FS::StylingAttributes>,
pub(crate) data: ButtonData<FD>,
}
impl<FD: FormToolData, FS: FormStyle> ButtonBuilder<FD, FS> {
fn new() -> Self {
ButtonBuilder {
styles: Vec::default(),
data: ButtonData::default(),
}
}
pub fn style(mut self, style: FS::StylingAttributes) -> Self {
self.styles.push(style);
self
}
pub fn text(mut self, text: impl ToString) -> Self { pub fn text(mut self, text: impl ToString) -> Self {
self.data.text = text.to_string(); self.data.text = text.to_string();
self self

View File

@ -4,9 +4,8 @@ use leptos::{CollectView, RwSignal};
impl<FD: FormToolData, FS: FormStyle> FormBuilder<FD, FS> { impl<FD: FormToolData, FS: FormStyle> FormBuilder<FD, FS> {
pub fn group(mut self, builder: impl Fn(FormBuilder<FD, FS>) -> FormBuilder<FD, FS>) -> Self { pub fn group(mut self, builder: impl Fn(FormBuilder<FD, FS>) -> FormBuilder<FD, FS>) -> Self {
let mut group_builder = FormBuilder::new_group(self.fd, self.fs); let mut group_builder = FormBuilder::new();
group_builder = builder(group_builder); group_builder = builder(group_builder);
self.fs = group_builder.fs; // take the style back
for validation in group_builder.validations { for validation in group_builder.validations {
self.validations.push(validation); self.validations.push(validation);
@ -16,10 +15,13 @@ impl<FD: FormToolData, FS: FormStyle> FormBuilder<FD, FS> {
let (views, validation_cbs): (Vec<_>, Vec<_>) = group_builder let (views, validation_cbs): (Vec<_>, Vec<_>) = group_builder
.render_fns .render_fns
.into_iter() .into_iter()
.map(|r_fn| r_fn(&fs, fd)) .map(|r_fn| r_fn(fs, fd))
.unzip(); .unzip();
let view = fs.group(views.collect_view(), group_builder.styles); let view = fs.group(super::ControlRenderData {
data: views.collect_view(),
styles: group_builder.styles,
});
let validation_cb = move || { let validation_cb = move || {
let mut success = true; let mut success = true;
for validation in validation_cbs.iter().flatten() { for validation in validation_cbs.iter().flatten() {

View File

@ -16,7 +16,6 @@ impl VanityControlData for HeadingData {
fs.heading(control) fs.heading(control)
} }
} }
// TODO: impl GetterVanityControl
impl<FD: FormToolData, FS: FormStyle> FormBuilder<FD, FS> { impl<FD: FormToolData, FS: FormStyle> FormBuilder<FD, FS> {
pub fn heading( pub fn heading(

View File

@ -70,8 +70,8 @@ pub trait ValidatedControlData: ControlData {}
/// The data needed to render a interactive control of type `C`. /// The data needed to render a interactive control of type `C`.
pub struct ControlRenderData<FS: FormStyle + ?Sized, C: ?Sized> { pub struct ControlRenderData<FS: FormStyle + ?Sized, C: ?Sized> {
pub data: Box<C>, pub styles: Vec<FS::StylingAttributes>,
pub style: Vec<FS::StylingAttributes>, pub data: C,
} }
/// The data needed to render a read-only control of type `C`. /// The data needed to render a read-only control of type `C`.
@ -100,8 +100,8 @@ impl<FD: FormToolData, FS: FormStyle, C: VanityControlData> VanityControlBuilder
pub(crate) fn build(self) -> BuiltVanityControlData<FD, FS, C> { pub(crate) fn build(self) -> BuiltVanityControlData<FD, FS, C> {
BuiltVanityControlData { BuiltVanityControlData {
render_data: ControlRenderData { render_data: ControlRenderData {
data: Box::new(self.data), data: self.data,
style: self.style_attributes, styles: self.style_attributes,
}, },
getter: self.getter, getter: self.getter,
} }
@ -208,8 +208,8 @@ impl<FD: FormToolData, FS: FormStyle, C: ControlData, FDT> ControlBuilder<FD, FS
Ok(BuiltControlData { Ok(BuiltControlData {
render_data: ControlRenderData { render_data: ControlRenderData {
data: Box::new(self.data), data: self.data,
style: self.style_attributes, styles: self.style_attributes,
}, },
getter, getter,
setter, setter,

View File

@ -1,15 +1,12 @@
use leptos::{Signal, View};
use super::{ControlBuilder, ControlData, ControlRenderData, ValidatedControlData}; use super::{ControlBuilder, ControlData, ControlRenderData, ValidatedControlData};
use crate::{form::FormToolData, form_builder::FormBuilder, styles::FormStyle}; use crate::{form::FormToolData, form_builder::FormBuilder, styles::FormStyle};
use leptos::{Signal, View};
// TODO: have an option to have a display string and a value string in the options field
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct SelectData { pub struct SelectData {
pub(crate) name: String, pub(crate) name: String,
pub(crate) label: Option<String>, pub(crate) label: Option<String>,
pub(crate) options: Vec<String>, pub(crate) options: Vec<(String, String)>,
} }
impl ControlData for SelectData { impl ControlData for SelectData {
@ -50,13 +47,36 @@ impl<FD: FormToolData, FS: FormStyle, FDT> ControlBuilder<FD, FS, SelectData, FD
} }
pub fn with_option(mut self, option: impl ToString) -> Self { pub fn with_option(mut self, option: impl ToString) -> Self {
self.data.options.push(option.to_string()); self.data
.options
.push((option.to_string(), option.to_string()));
self
}
pub fn with_option_valued(mut self, display: impl ToString, value: impl ToString) -> Self {
self.data
.options
.push((display.to_string(), value.to_string()));
self self
} }
pub fn with_options(mut self, options: impl Iterator<Item = impl ToString>) -> Self { pub fn with_options(mut self, options: impl Iterator<Item = impl ToString>) -> Self {
for option in options { for option in options {
self.data.options.push(option.to_string()); self.data
.options
.push((option.to_string(), option.to_string()));
}
self
}
pub fn with_options_valued(
mut self,
options: impl Iterator<Item = (impl ToString, impl ToString)>,
) -> Self {
for option in options {
self.data
.options
.push((option.0.to_string(), option.1.to_string()));
} }
self self
} }

View File

@ -78,7 +78,7 @@ impl<FD: FormToolData> IntoView for Form<FD> {
/// ///
/// 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
/// to physically lay out a form, and how that data should be parsed and validated. /// to physically lay out a form, and how that data should be parsed and validated.
pub trait FormToolData: Default + Clone + 'static { pub trait FormToolData: Clone + 'static {
type Style: FormStyle; type Style: FormStyle;
/// Defines how the form should be layed out and how the data should be parsed and validated. /// Defines how the form should be layed out and how the data should be parsed and validated.
@ -97,9 +97,9 @@ pub trait FormToolData: Default + Clone + 'static {
/// component. Call [`get_action_form`]\() to get the /// component. Call [`get_action_form`]\() to get the
/// [`ActionForm`](leptos_router::ActionForm) version. /// [`ActionForm`](leptos_router::ActionForm) version.
fn get_form(self, action: impl ToString, style: Self::Style) -> Form<Self> { fn get_form(self, action: impl ToString, style: Self::Style) -> Form<Self> {
let builder = FormBuilder::new(self, style); let builder = FormBuilder::new();
let builder = Self::build_form(builder); let builder = Self::build_form(builder);
builder.build_plain_form(action.to_string()) builder.build_plain_form(action.to_string(), self, style)
} }
/// Constructs a [`Form`] for this [`FormToolData`] type. /// Constructs a [`Form`] for this [`FormToolData`] type.
@ -118,9 +118,9 @@ pub trait FormToolData: Default + Clone + 'static {
<<ServFn::Client as Client<ServFn::Error>>::Request as ClientReq<ServFn::Error>>::FormData: <<ServFn::Client as Client<ServFn::Error>>::Request as ClientReq<ServFn::Error>>::FormData:
From<FormData>, From<FormData>,
{ {
let builder = FormBuilder::new(self, style); let builder = FormBuilder::new();
let builder = Self::build_form(builder); let builder = Self::build_form(builder);
builder.build_action_form(action) builder.build_action_form(action, self, style)
} }
/// Gets a [`Validator`] for this [`ToolFormData`]. /// Gets a [`Validator`] for this [`ToolFormData`].
@ -132,7 +132,7 @@ pub trait FormToolData: Default + Clone + 'static {
/// However, the code to render the views are not configured out, it /// However, the code to render the views are not configured out, it
/// simply doesn't run, so the view needs to compile even on the server. /// simply doesn't run, so the view needs to compile even on the server.
fn get_validator() -> FormValidator<Self> { fn get_validator() -> FormValidator<Self> {
let builder = FormBuilder::new(Self::default(), Self::Style::default()); let builder = FormBuilder::new();
let builder = Self::build_form(builder); let builder = Self::build_form(builder);
builder.validator() builder.validator()
} }

View File

@ -1,8 +1,8 @@
use crate::{ use crate::{
controls::{ controls::{
BuiltControlData, BuiltVanityControlData, ControlBuilder, ControlData, FieldGetter, BuiltControlData, BuiltVanityControlData, ControlBuilder, ControlData, ControlRenderData,
FieldSetter, ParseFn, RenderFn, UnparseFn, ValidationCb, ValidationFn, FieldSetter, ParseFn, RenderFn, ValidationCb, ValidationFn, VanityControlBuilder,
VanityControlBuilder, VanityControlData, VanityControlData,
}, },
form::{Form, FormToolData, FormValidator}, form::{Form, FormToolData, FormValidator},
styles::FormStyle, styles::FormStyle,
@ -20,10 +20,6 @@ use web_sys::{FormData, SubmitEvent};
/// ///
/// This builder allows you to specify what components should make up the form. /// This builder allows you to specify what components should make up the form.
pub struct FormBuilder<FD: FormToolData, FS: FormStyle> { pub struct FormBuilder<FD: FormToolData, FS: FormStyle> {
/// The [`ToolFormData`] signal.
pub(crate) fd: RwSignal<FD>,
/// The [`FormStyle`].
pub(crate) fs: FS,
/// The list of [`ValidationFn`]s. /// The list of [`ValidationFn`]s.
pub(crate) validations: Vec<Rc<dyn ValidationFn<FD>>>, pub(crate) validations: Vec<Rc<dyn ValidationFn<FD>>>,
/// The list of functions that will render the form. /// The list of functions that will render the form.
@ -34,21 +30,8 @@ pub struct FormBuilder<FD: FormToolData, FS: FormStyle> {
impl<FD: FormToolData, FS: FormStyle> FormBuilder<FD, FS> { impl<FD: FormToolData, FS: FormStyle> FormBuilder<FD, FS> {
/// Creates a new [`FormBuilder`] /// Creates a new [`FormBuilder`]
pub(crate) fn new(starting_data: FD, form_style: FS) -> FormBuilder<FD, FS> { pub(crate) fn new() -> FormBuilder<FD, FS> {
let fd = create_rw_signal(starting_data);
FormBuilder { FormBuilder {
fd,
fs: form_style,
validations: Vec::new(),
render_fns: Vec::new(),
styles: Vec::new(),
}
}
pub(crate) fn new_group(fd: RwSignal<FD>, fs: FS) -> FormBuilder<FD, FS> {
FormBuilder {
fd,
fs,
validations: Vec::new(), validations: Vec::new(),
render_fns: Vec::new(), render_fns: Vec::new(),
styles: Vec::new(), styles: Vec::new(),
@ -104,33 +87,17 @@ impl<FD: FormToolData, FS: FormStyle> FormBuilder<FD, FS> {
&mut self, &mut self,
control: ControlBuilder<FD, FS, C, FDT>, control: ControlBuilder<FD, FS, C, FDT>,
) { ) {
let BuiltControlData { let built_control_data = match control.build() {
render_data,
getter,
setter,
parse_fn,
unparse_fn,
validation_fn,
} = match control.build() {
Ok(c) => c, Ok(c) => c,
Err(e) => panic!("Invalid Component: {}", e), Err(e) => panic!("Invalid Component: {}", e),
}; };
if let Some(ref validation_fn) = validation_fn { if let Some(ref validation_fn) = built_control_data.validation_fn {
self.validations.push(validation_fn.clone()); self.validations.push(validation_fn.clone());
} }
let render_fn = move |fs: &FS, fd: RwSignal<FD>| { let render_fn = move |fs: &FS, fd: RwSignal<FD>| {
let (view, cb) = Self::build_control_view( let (view, cb) = Self::build_control_view(fd, fs, built_control_data);
fd,
fs,
getter,
setter,
unparse_fn,
parse_fn,
validation_fn,
render_data,
);
(view, Some(cb)) (view, Some(cb))
}; };
@ -140,13 +107,17 @@ impl<FD: FormToolData, FS: FormStyle> FormBuilder<FD, FS> {
fn build_control_view<C: ControlData, FDT: 'static>( fn build_control_view<C: ControlData, FDT: 'static>(
fd: RwSignal<FD>, fd: RwSignal<FD>,
fs: &FS, fs: &FS,
getter: Rc<dyn FieldGetter<FD, FDT>>, control_data: BuiltControlData<FD, FS, C, 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>) { ) -> (View, Box<dyn ValidationCb>) {
let BuiltControlData {
render_data,
getter,
setter,
parse_fn,
unparse_fn,
validation_fn,
} = control_data;
let (validation_signal, validation_signal_set) = create_signal(Ok(())); let (validation_signal, validation_signal_set) = create_signal(Ok(()));
let validation_fn_clone = validation_fn.clone(); let validation_fn_clone = validation_fn.clone();
let value_getter = move || { let value_getter = move || {
@ -232,19 +203,26 @@ impl<FD: FormToolData, FS: FormStyle> FormBuilder<FD, FS> {
pub(crate) fn build_action_form<ServFn>( pub(crate) fn build_action_form<ServFn>(
self, self,
action: Action<ServFn, Result<ServFn::Output, ServerFnError<ServFn::Error>>>, action: Action<ServFn, Result<ServFn::Output, ServerFnError<ServFn::Error>>>,
fd: FD,
fs: FS,
) -> Form<FD> ) -> Form<FD>
where where
ServFn: DeserializeOwned + ServerFn<InputEncoding = PostUrl> + 'static, ServFn: DeserializeOwned + ServerFn<InputEncoding = PostUrl> + 'static,
<<ServFn::Client as Client<ServFn::Error>>::Request as ClientReq<ServFn::Error>>::FormData: <<ServFn::Client as Client<ServFn::Error>>::Request as ClientReq<ServFn::Error>>::FormData:
From<FormData>, From<FormData>,
{ {
let fd = create_rw_signal(fd);
let (views, validation_cbs): (Vec<_>, Vec<_>) = self let (views, validation_cbs): (Vec<_>, Vec<_>) = self
.render_fns .render_fns
.into_iter() .into_iter()
.map(|r_fn| r_fn(&self.fs, self.fd)) .map(|r_fn| r_fn(&fs, fd))
.unzip(); .unzip();
let elements = self.fs.form_frame(views.into_view(), self.styles); let elements = fs.form_frame(ControlRenderData {
data: views.into_view(),
styles: self.styles,
});
let on_submit = move |ev: SubmitEvent| { let on_submit = move |ev: SubmitEvent| {
let mut failed = false; let mut failed = false;
@ -265,20 +243,25 @@ impl<FD: FormToolData, FS: FormStyle> FormBuilder<FD, FS> {
}; };
Form { Form {
fd: self.fd, fd,
validations: self.validations, validations: self.validations,
view, view,
} }
} }
pub(crate) fn build_plain_form(self, url: String) -> Form<FD> { pub(crate) fn build_plain_form(self, url: String, fd: FD, fs: FS) -> Form<FD> {
let fd = create_rw_signal(fd);
let (views, validation_cbs): (Vec<_>, Vec<_>) = self let (views, validation_cbs): (Vec<_>, Vec<_>) = self
.render_fns .render_fns
.into_iter() .into_iter()
.map(|r_fn| r_fn(&self.fs, self.fd)) .map(|r_fn| r_fn(&fs, fd))
.unzip(); .unzip();
let elements = self.fs.form_frame(views.into_view(), self.styles); let elements = fs.form_frame(ControlRenderData {
data: views.into_view(),
styles: self.styles,
});
let on_submit = move |ev: SubmitEvent| { let on_submit = move |ev: SubmitEvent| {
let mut failed = false; let mut failed = false;
@ -299,7 +282,7 @@ impl<FD: FormToolData, FS: FormStyle> FormBuilder<FD, FS> {
}; };
Form { Form {
fd: self.fd, fd,
validations: self.validations, validations: self.validations,
view, view,
} }

View File

@ -1,8 +1,11 @@
use super::FormStyle; use super::FormStyle;
use crate::controls::{ use crate::{
checkbox::CheckboxData, heading::HeadingData, hidden::HiddenData, output::OutputData, controls::{
select::SelectData, spacer::SpacerData, submit::SubmitData, text_area::TextAreaData, button::ButtonData, checkbox::CheckboxData, heading::HeadingData, hidden::HiddenData,
text_input::TextInputData, ControlData, ControlRenderData, output::OutputData, select::SelectData, spacer::SpacerData, submit::SubmitData,
text_area::TextAreaData, text_input::TextInputData, ControlData, ControlRenderData,
},
FormToolData,
}; };
use leptos::*; use leptos::*;
use std::rc::Rc; use std::rc::Rc;
@ -18,8 +21,8 @@ pub struct GridFormStyle;
impl FormStyle for GridFormStyle { impl FormStyle for GridFormStyle {
type StylingAttributes = GridFormStylingAttributes; type StylingAttributes = GridFormStylingAttributes;
fn form_frame(&self, children: View, _styles: Vec<Self::StylingAttributes>) -> View { fn form_frame(&self, form: ControlRenderData<Self, View>) -> View {
view! { <div class="form_grid">{children}</div> }.into_view() view! { <div class="form_grid">{form.data}</div> }.into_view()
} }
fn heading(&self, control: ControlRenderData<Self, HeadingData>) -> View { fn heading(&self, control: ControlRenderData<Self, HeadingData>) -> View {
@ -35,7 +38,7 @@ impl FormStyle for GridFormStyle {
) -> View { ) -> View {
// TODO: extract this to a common thing // TODO: extract this to a common thing
let mut width = 1; let mut width = 1;
for style in control.style { for style in control.styles {
match style { match style {
GridFormStylingAttributes::Width(w) => width = w, GridFormStylingAttributes::Width(w) => width = w,
} }
@ -78,14 +81,10 @@ impl FormStyle for GridFormStyle {
.data .data
.options .options
.into_iter() .into_iter()
.map(|value| { .map(|(display, value)| {
let cloned_value = value.clone();
view! { view! {
<option <option value=value.clone() selected=move || { value_getter.get() == *value }>
value=value.clone() {display.clone()}
selected=move || {value_getter.get() == *cloned_value}
>
{value.clone()}
</option> </option>
} }
}) })
@ -112,10 +111,7 @@ impl FormStyle for GridFormStyle {
} }
fn submit(&self, control: ControlRenderData<Self, SubmitData>) -> View { fn submit(&self, control: ControlRenderData<Self, SubmitData>) -> View {
view! { view! { <input type="submit" value=control.data.text class="form_submit"/> }.into_view()
<input type="submit" value=control.data.text class="form_submit"/>
}
.into_view()
} }
fn text_area( fn text_area(
@ -137,7 +133,8 @@ impl FormStyle for GridFormStyle {
on:change=move |ev| { on:change=move |ev| {
value_setter(event_target_value(&ev)); value_setter(event_target_value(&ev));
} }
></textarea> >
</textarea>
</div> </div>
} }
@ -168,7 +165,7 @@ impl FormStyle for GridFormStyle {
validation_state: Signal<Result<(), String>>, validation_state: Signal<Result<(), String>>,
) -> View { ) -> View {
let mut width = 1; let mut width = 1;
for style in control.style { for style in control.styles {
match style { match style {
GridFormStylingAttributes::Width(w) => width = w, GridFormStylingAttributes::Width(w) => width = w,
} }
@ -198,6 +195,7 @@ impl FormStyle for GridFormStyle {
} }
} }
/> />
<label for=&o>{&o}</label> <label for=&o>{&o}</label>
<br/> <br/>
} }
@ -230,7 +228,7 @@ impl FormStyle for GridFormStyle {
value_setter: Box<dyn Fn(<CheckboxData as ControlData>::ReturnType)>, value_setter: Box<dyn Fn(<CheckboxData as ControlData>::ReturnType)>,
) -> View { ) -> View {
let mut width = 1; let mut width = 1;
for style in control.style { for style in control.styles {
match style { match style {
GridFormStylingAttributes::Width(w) => width = w, GridFormStylingAttributes::Width(w) => width = w,
} }
@ -270,7 +268,7 @@ impl FormStyle for GridFormStyle {
validation_state: Signal<Result<(), String>>, validation_state: Signal<Result<(), String>>,
) -> View { ) -> View {
let mut width = 1; let mut width = 1;
for style in control.style { for style in control.styles {
match style { match style {
GridFormStylingAttributes::Width(w) => width = w, GridFormStylingAttributes::Width(w) => width = w,
} }
@ -317,7 +315,7 @@ impl FormStyle for GridFormStyle {
validation_state: Signal<Result<(), String>>, validation_state: Signal<Result<(), String>>,
) -> View { ) -> View {
let mut width = 1; let mut width = 1;
for style in control.style { for style in control.styles {
match style { match style {
GridFormStylingAttributes::Width(w) => width = w, GridFormStylingAttributes::Width(w) => width = w,
} }
@ -352,37 +350,41 @@ impl FormStyle for GridFormStyle {
.into_view() .into_view()
} }
fn button<FD: crate::FormToolData>( fn button<FD: FormToolData>(
&self, &self,
control: ControlRenderData<Self, crate::controls::button::ButtonData<FD>>, control: ControlRenderData<Self, ButtonData<FD>>,
data_signal: RwSignal<FD>,
) -> View { ) -> View {
let mut width = 1; let mut width = 1;
for style in control.style { for style in control.styles {
match style { match style {
GridFormStylingAttributes::Width(w) => width = w, GridFormStylingAttributes::Width(w) => width = w,
} }
} }
let action = control.data.action.clone(); let action = control.data.action.clone();
let signal = control.data.fd_signal.clone();
let on_click = move |ev: MouseEvent| { let on_click = move |ev: MouseEvent| {
if let Some(action) = action.clone() { if let Some(action) = action.clone() {
signal.update(|fd| action(ev, fd)); data_signal.update(|fd| action(ev, fd));
} }
}; };
view! { view! {
<button type="button" class="form_button" on:click=on_click style:grid-column=format!("span {}", width)> <button
type="button"
class="form_button"
on:click=on_click
style:grid-column=format!("span {}", width)
>
{control.data.text} {control.data.text}
</button> </button>
} }
.into_view() .into_view()
} }
// TODO: change this and form frame to use ControlRenderData fn group(&self, group: ControlRenderData<Self, View>) -> View {
fn group(&self, inner: View, styles: Vec<Self::StylingAttributes>) -> View {
let mut width = 12; let mut width = 12;
for style in styles { for style in group.styles {
match style { match style {
GridFormStylingAttributes::Width(w) => width = w, GridFormStylingAttributes::Width(w) => width = w,
} }
@ -390,7 +392,7 @@ impl FormStyle for GridFormStyle {
view! { view! {
<div class="form_group form_grid" style:grid-column=format!("span {}", width)> <div class="form_group form_grid" style:grid-column=format!("span {}", width)>
{inner} {group.data}
</div> </div>
} }
.into_view() .into_view()
@ -398,15 +400,13 @@ impl FormStyle for GridFormStyle {
fn spacer(&self, control: ControlRenderData<Self, SpacerData>) -> View { fn spacer(&self, control: ControlRenderData<Self, SpacerData>) -> View {
let mut width = 12; let mut width = 12;
for style in control.style { for style in control.styles {
match style { match style {
GridFormStylingAttributes::Width(w) => width = w, GridFormStylingAttributes::Width(w) => width = w,
} }
} }
view! { view! { <div style:grid-column=format!("span {}", width) style:height=control.data.height></div> }
<div style:grid-column=format!("span {}", width) style:height=control.data.height/>
}
.into_view() .into_view()
} }
} }

View File

@ -9,11 +9,11 @@ use crate::{
}, },
FormToolData, FormToolData,
}; };
use leptos::{Signal, View}; use leptos::{RwSignal, Signal, View};
pub use grid_form::{GridFormStyle, GridFormStylingAttributes}; pub use grid_form::{GridFormStyle, GridFormStylingAttributes};
pub trait FormStyle: Default + 'static { pub trait FormStyle: 'static {
type StylingAttributes; type StylingAttributes;
/// Render any containing components for the form. /// Render any containing components for the form.
@ -23,7 +23,7 @@ pub trait FormStyle: Default + 'static {
/// ///
/// Do NOT wrap it in an actual `form` element; any /// Do NOT wrap it in an actual `form` element; any
/// wrapping should be done with `div` or similar elements. /// wrapping should be done with `div` or similar elements.
fn form_frame(&self, children: View, style: Vec<Self::StylingAttributes>) -> View; fn form_frame(&self, form: ControlRenderData<Self, View>) -> View;
fn heading(&self, control: ControlRenderData<Self, HeadingData>) -> View; fn heading(&self, control: ControlRenderData<Self, HeadingData>) -> View;
fn hidden( fn hidden(
&self, &self,
@ -58,7 +58,11 @@ pub trait FormStyle: Default + 'static {
value_setter: Box<dyn Fn(<SelectData as ControlData>::ReturnType)>, value_setter: Box<dyn Fn(<SelectData as ControlData>::ReturnType)>,
validation_state: Signal<Result<(), String>>, validation_state: Signal<Result<(), String>>,
) -> View; ) -> View;
fn button<FD: FormToolData>(&self, control: ControlRenderData<Self, ButtonData<FD>>) -> View; fn button<FD: FormToolData>(
&self,
control: ControlRenderData<Self, ButtonData<FD>>,
data_signal: RwSignal<FD>,
) -> View;
fn checkbox( fn checkbox(
&self, &self,
control: ControlRenderData<Self, CheckboxData>, control: ControlRenderData<Self, CheckboxData>,
@ -85,8 +89,7 @@ pub trait FormStyle: Default + 'static {
validation_state: Signal<Result<(), String>>, validation_state: Signal<Result<(), String>>,
) -> View; ) -> View;
fn submit(&self, control: ControlRenderData<Self, SubmitData>) -> View; fn submit(&self, control: ControlRenderData<Self, SubmitData>) -> View;
// TODO: test custom component
fn custom_component(&self, view: View) -> View; fn custom_component(&self, view: View) -> View;
fn group(&self, inner: View, style: Vec<Self::StylingAttributes>) -> View; fn group(&self, group: ControlRenderData<Self, View>) -> View;
fn spacer(&self, control: ControlRenderData<Self, SpacerData>) -> View; fn spacer(&self, control: ControlRenderData<Self, SpacerData>) -> View;
} }

View File

@ -1,6 +1,12 @@
use crate::{controls::ValidationFn, FormToolData}; use crate::{controls::ValidationFn, FormToolData};
use std::fmt::Display; use std::fmt::Display;
/// A function that validates a field.
///
/// This is similar to [`ValidationFn`](crate::controls::ValidationFn)
/// but takes a &str for the name of the field for improved error messages.
type ValidationBuilderFn<T> = dyn Fn(&str, &T) -> Result<(), String> + 'static;
/// A helper builder that allows you to specify a validation function /// A helper builder that allows you to specify a validation function
/// declaritivly /// declaritivly
/// ///
@ -14,7 +20,7 @@ pub struct ValidationBuilder<FD: FormToolData, T: 'static> {
/// The getter function for the field to validate. /// The getter function for the field to validate.
field_fn: Box<dyn Fn(&FD) -> &T + 'static>, field_fn: Box<dyn Fn(&FD) -> &T + 'static>,
/// The functions to be called when validating. /// The functions to be called when validating.
functions: Vec<Box<dyn Fn(&str, &T) -> Result<(), String> + 'static>>, functions: Vec<Box<ValidationBuilderFn<T>>>,
} }
impl<FD: FormToolData, T: 'static> ValidationBuilder<FD, T> { impl<FD: FormToolData, T: 'static> ValidationBuilder<FD, T> {