diff --git a/src/controls/button.rs b/src/controls/button.rs index 1b72b24..a3d39fe 100644 --- a/src/controls/button.rs +++ b/src/controls/button.rs @@ -1,48 +1,67 @@ -use super::{ControlRenderData, VanityControlBuilder, VanityControlData}; +use super::ControlRenderData; use crate::{form::FormToolData, form_builder::FormBuilder, styles::FormStyle}; -use leptos::{RwSignal, Signal, View}; +use leptos::RwSignal; use std::rc::Rc; use web_sys::MouseEvent; +type ButtonAction = dyn Fn(MouseEvent, &mut FD); + #[derive(Clone)] pub struct ButtonData { pub(crate) text: String, - pub(crate) action: Option>, - - // this will need to be set before calling the build method - pub(crate) fd_signal: RwSignal, + pub(crate) action: Option>>, } - -impl VanityControlData for ButtonData { - fn build_control( - fs: &FS, - control: ControlRenderData, - _value_getter: Option>, - ) -> View { - fs.button(control) +impl Default for ButtonData { + fn default() -> Self { + ButtonData { + text: String::default(), + action: None, + } } } impl FormBuilder { pub fn button( mut self, - builder: impl Fn( - VanityControlBuilder>, - ) -> VanityControlBuilder>, + builder: impl Fn(ButtonBuilder) -> ButtonBuilder, ) -> Self { - let data = ButtonData { - text: String::default(), - action: None, - fd_signal: self.fd, + let button_builder = ButtonBuilder::new(); + let control = builder(button_builder); + + let render_data = ControlRenderData { + data: control.data, + styles: control.styles, }; - let vanity_builder = VanityControlBuilder::new(data); - let control = builder(vanity_builder); - self.add_vanity(control); + + let render_fn = move |fs: &FS, fd: RwSignal| { + let view = fs.button(render_data, fd); + (view, None) + }; + self.render_fns.push(Box::new(render_fn)); + self } } -impl VanityControlBuilder> { +#[derive(Clone)] +pub struct ButtonBuilder { + pub(crate) styles: Vec, + pub(crate) data: ButtonData, +} + +impl ButtonBuilder { + 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 { self.data.text = text.to_string(); self diff --git a/src/controls/group.rs b/src/controls/group.rs index 1832f41..27711ac 100644 --- a/src/controls/group.rs +++ b/src/controls/group.rs @@ -4,9 +4,8 @@ use leptos::{CollectView, RwSignal}; impl FormBuilder { pub fn group(mut self, builder: impl Fn(FormBuilder) -> FormBuilder) -> Self { - let mut group_builder = FormBuilder::new_group(self.fd, self.fs); + let mut group_builder = FormBuilder::new(); group_builder = builder(group_builder); - self.fs = group_builder.fs; // take the style back for validation in group_builder.validations { self.validations.push(validation); @@ -16,10 +15,13 @@ impl FormBuilder { let (views, validation_cbs): (Vec<_>, Vec<_>) = group_builder .render_fns .into_iter() - .map(|r_fn| r_fn(&fs, fd)) + .map(|r_fn| r_fn(fs, fd)) .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 mut success = true; for validation in validation_cbs.iter().flatten() { diff --git a/src/controls/heading.rs b/src/controls/heading.rs index c9d05a9..1f95191 100644 --- a/src/controls/heading.rs +++ b/src/controls/heading.rs @@ -16,7 +16,6 @@ impl VanityControlData for HeadingData { fs.heading(control) } } -// TODO: impl GetterVanityControl impl FormBuilder { pub fn heading( diff --git a/src/controls/mod.rs b/src/controls/mod.rs index 751cf82..5d8c113 100644 --- a/src/controls/mod.rs +++ b/src/controls/mod.rs @@ -70,8 +70,8 @@ pub trait ValidatedControlData: ControlData {} /// The data needed to render a interactive control of type `C`. pub struct ControlRenderData { - pub data: Box, - pub style: Vec, + pub styles: Vec, + pub data: C, } /// The data needed to render a read-only control of type `C`. @@ -100,8 +100,8 @@ impl VanityControlBuilder pub(crate) fn build(self) -> BuiltVanityControlData { BuiltVanityControlData { render_data: ControlRenderData { - data: Box::new(self.data), - style: self.style_attributes, + data: self.data, + styles: self.style_attributes, }, getter: self.getter, } @@ -208,8 +208,8 @@ impl ControlBuilder, - pub(crate) options: Vec, + pub(crate) options: Vec<(String, String)>, } impl ControlData for SelectData { @@ -50,13 +47,36 @@ impl ControlBuilder 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 } pub fn with_options(mut self, options: impl Iterator) -> Self { 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, + ) -> Self { + for option in options { + self.data + .options + .push((option.0.to_string(), option.1.to_string())); } self } diff --git a/src/form.rs b/src/form.rs index c5ed3d8..3d62d03 100644 --- a/src/form.rs +++ b/src/form.rs @@ -78,7 +78,7 @@ impl IntoView for Form { /// /// 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 FormToolData: Default + Clone + 'static { +pub trait FormToolData: Clone + 'static { type Style: FormStyle; /// 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 /// [`ActionForm`](leptos_router::ActionForm) version. fn get_form(self, action: impl ToString, style: Self::Style) -> Form { - let builder = FormBuilder::new(self, style); + let builder = FormBuilder::new(); 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. @@ -118,9 +118,9 @@ pub trait FormToolData: Default + Clone + 'static { <>::Request as ClientReq>::FormData: From, { - let builder = FormBuilder::new(self, style); + let builder = FormBuilder::new(); let builder = Self::build_form(builder); - builder.build_action_form(action) + builder.build_action_form(action, self, style) } /// 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 /// simply doesn't run, so the view needs to compile even on the server. fn get_validator() -> FormValidator { - let builder = FormBuilder::new(Self::default(), Self::Style::default()); + let builder = FormBuilder::new(); let builder = Self::build_form(builder); builder.validator() } diff --git a/src/form_builder.rs b/src/form_builder.rs index 49cba10..de96f90 100644 --- a/src/form_builder.rs +++ b/src/form_builder.rs @@ -1,8 +1,8 @@ use crate::{ controls::{ - BuiltControlData, BuiltVanityControlData, ControlBuilder, ControlData, FieldGetter, - FieldSetter, ParseFn, RenderFn, UnparseFn, ValidationCb, ValidationFn, - VanityControlBuilder, VanityControlData, + BuiltControlData, BuiltVanityControlData, ControlBuilder, ControlData, ControlRenderData, + FieldSetter, ParseFn, RenderFn, ValidationCb, ValidationFn, VanityControlBuilder, + VanityControlData, }, form::{Form, FormToolData, FormValidator}, styles::FormStyle, @@ -20,10 +20,6 @@ use web_sys::{FormData, SubmitEvent}; /// /// This builder allows you to specify what components should make up the form. pub struct FormBuilder { - /// The [`ToolFormData`] signal. - pub(crate) fd: RwSignal, - /// The [`FormStyle`]. - pub(crate) fs: FS, /// The list of [`ValidationFn`]s. pub(crate) validations: Vec>>, /// The list of functions that will render the form. @@ -34,21 +30,8 @@ pub struct FormBuilder { impl FormBuilder { /// Creates a new [`FormBuilder`] - pub(crate) fn new(starting_data: FD, form_style: FS) -> FormBuilder { - let fd = create_rw_signal(starting_data); + pub(crate) fn new() -> FormBuilder { FormBuilder { - fd, - fs: form_style, - validations: Vec::new(), - render_fns: Vec::new(), - styles: Vec::new(), - } - } - - pub(crate) fn new_group(fd: RwSignal, fs: FS) -> FormBuilder { - FormBuilder { - fd, - fs, validations: Vec::new(), render_fns: Vec::new(), styles: Vec::new(), @@ -104,33 +87,17 @@ impl FormBuilder { &mut self, control: ControlBuilder, ) { - let BuiltControlData { - render_data, - getter, - setter, - parse_fn, - unparse_fn, - validation_fn, - } = match control.build() { + let built_control_data = match control.build() { Ok(c) => c, 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()); } let render_fn = move |fs: &FS, fd: RwSignal| { - let (view, cb) = Self::build_control_view( - fd, - fs, - getter, - setter, - unparse_fn, - parse_fn, - validation_fn, - render_data, - ); + let (view, cb) = Self::build_control_view(fd, fs, built_control_data); (view, Some(cb)) }; @@ -140,13 +107,17 @@ impl FormBuilder { fn build_control_view( fd: RwSignal, fs: &FS, - getter: Rc>, - setter: Rc>, - unparse_fn: Box::ReturnType, FDT>>, - parse_fn: Box::ReturnType, FDT>>, - validation_fn: Option>>, - render_data: crate::controls::ControlRenderData, + control_data: BuiltControlData, ) -> (View, Box) { + 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_fn_clone = validation_fn.clone(); let value_getter = move || { @@ -232,19 +203,26 @@ impl FormBuilder { pub(crate) fn build_action_form( self, action: Action>>, + fd: FD, + fs: FS, ) -> Form where ServFn: DeserializeOwned + ServerFn + 'static, <>::Request as ClientReq>::FormData: From, { + let fd = create_rw_signal(fd); + let (views, validation_cbs): (Vec<_>, Vec<_>) = self .render_fns .into_iter() - .map(|r_fn| r_fn(&self.fs, self.fd)) + .map(|r_fn| r_fn(&fs, fd)) .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 mut failed = false; @@ -265,20 +243,25 @@ impl FormBuilder { }; Form { - fd: self.fd, + fd, validations: self.validations, view, } } - pub(crate) fn build_plain_form(self, url: String) -> Form { + pub(crate) fn build_plain_form(self, url: String, fd: FD, fs: FS) -> Form { + let fd = create_rw_signal(fd); + let (views, validation_cbs): (Vec<_>, Vec<_>) = self .render_fns .into_iter() - .map(|r_fn| r_fn(&self.fs, self.fd)) + .map(|r_fn| r_fn(&fs, fd)) .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 mut failed = false; @@ -299,7 +282,7 @@ impl FormBuilder { }; Form { - fd: self.fd, + fd, validations: self.validations, view, } diff --git a/src/styles/grid_form.rs b/src/styles/grid_form.rs index dc6cf9e..3a76b16 100644 --- a/src/styles/grid_form.rs +++ b/src/styles/grid_form.rs @@ -1,8 +1,11 @@ use super::FormStyle; -use crate::controls::{ - checkbox::CheckboxData, heading::HeadingData, hidden::HiddenData, output::OutputData, - select::SelectData, spacer::SpacerData, submit::SubmitData, text_area::TextAreaData, - text_input::TextInputData, ControlData, ControlRenderData, +use crate::{ + controls::{ + button::ButtonData, checkbox::CheckboxData, heading::HeadingData, hidden::HiddenData, + output::OutputData, select::SelectData, spacer::SpacerData, submit::SubmitData, + text_area::TextAreaData, text_input::TextInputData, ControlData, ControlRenderData, + }, + FormToolData, }; use leptos::*; use std::rc::Rc; @@ -18,8 +21,8 @@ pub struct GridFormStyle; impl FormStyle for GridFormStyle { type StylingAttributes = GridFormStylingAttributes; - fn form_frame(&self, children: View, _styles: Vec) -> View { - view! {
{children}
}.into_view() + fn form_frame(&self, form: ControlRenderData) -> View { + view! {
{form.data}
}.into_view() } fn heading(&self, control: ControlRenderData) -> View { @@ -35,7 +38,7 @@ impl FormStyle for GridFormStyle { ) -> View { // TODO: extract this to a common thing let mut width = 1; - for style in control.style { + for style in control.styles { match style { GridFormStylingAttributes::Width(w) => width = w, } @@ -78,14 +81,10 @@ impl FormStyle for GridFormStyle { .data .options .into_iter() - .map(|value| { - let cloned_value = value.clone(); + .map(|(display, value)| { view! { - } }) @@ -112,10 +111,7 @@ impl FormStyle for GridFormStyle { } fn submit(&self, control: ControlRenderData) -> View { - view! { - - } - .into_view() + view! { }.into_view() } fn text_area( @@ -137,7 +133,8 @@ impl FormStyle for GridFormStyle { on:change=move |ev| { value_setter(event_target_value(&ev)); } - > + > + } @@ -168,7 +165,7 @@ impl FormStyle for GridFormStyle { validation_state: Signal>, ) -> View { let mut width = 1; - for style in control.style { + for style in control.styles { match style { GridFormStylingAttributes::Width(w) => width = w, } @@ -198,6 +195,7 @@ impl FormStyle for GridFormStyle { } } /> +
} @@ -230,7 +228,7 @@ impl FormStyle for GridFormStyle { value_setter: Box::ReturnType)>, ) -> View { let mut width = 1; - for style in control.style { + for style in control.styles { match style { GridFormStylingAttributes::Width(w) => width = w, } @@ -270,7 +268,7 @@ impl FormStyle for GridFormStyle { validation_state: Signal>, ) -> View { let mut width = 1; - for style in control.style { + for style in control.styles { match style { GridFormStylingAttributes::Width(w) => width = w, } @@ -317,7 +315,7 @@ impl FormStyle for GridFormStyle { validation_state: Signal>, ) -> View { let mut width = 1; - for style in control.style { + for style in control.styles { match style { GridFormStylingAttributes::Width(w) => width = w, } @@ -352,37 +350,41 @@ impl FormStyle for GridFormStyle { .into_view() } - fn button( + fn button( &self, - control: ControlRenderData>, + control: ControlRenderData>, + data_signal: RwSignal, ) -> View { let mut width = 1; - for style in control.style { + for style in control.styles { match style { GridFormStylingAttributes::Width(w) => width = w, } } let action = control.data.action.clone(); - let signal = control.data.fd_signal.clone(); let on_click = move |ev: MouseEvent| { if let Some(action) = action.clone() { - signal.update(|fd| action(ev, fd)); + data_signal.update(|fd| action(ev, fd)); } }; view! { - } .into_view() } - // TODO: change this and form frame to use ControlRenderData - fn group(&self, inner: View, styles: Vec) -> View { + fn group(&self, group: ControlRenderData) -> View { let mut width = 12; - for style in styles { + for style in group.styles { match style { GridFormStylingAttributes::Width(w) => width = w, } @@ -390,7 +392,7 @@ impl FormStyle for GridFormStyle { view! {
- {inner} + {group.data}
} .into_view() @@ -398,15 +400,13 @@ impl FormStyle for GridFormStyle { fn spacer(&self, control: ControlRenderData) -> View { let mut width = 12; - for style in control.style { + for style in control.styles { match style { GridFormStylingAttributes::Width(w) => width = w, } } - view! { -
- } + view! {
} .into_view() } } diff --git a/src/styles/mod.rs b/src/styles/mod.rs index 36383f6..323d56f 100644 --- a/src/styles/mod.rs +++ b/src/styles/mod.rs @@ -9,11 +9,11 @@ use crate::{ }, FormToolData, }; -use leptos::{Signal, View}; +use leptos::{RwSignal, Signal, View}; pub use grid_form::{GridFormStyle, GridFormStylingAttributes}; -pub trait FormStyle: Default + 'static { +pub trait FormStyle: 'static { type StylingAttributes; /// 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 /// wrapping should be done with `div` or similar elements. - fn form_frame(&self, children: View, style: Vec) -> View; + fn form_frame(&self, form: ControlRenderData) -> View; fn heading(&self, control: ControlRenderData) -> View; fn hidden( &self, @@ -58,7 +58,11 @@ pub trait FormStyle: Default + 'static { value_setter: Box::ReturnType)>, validation_state: Signal>, ) -> View; - fn button(&self, control: ControlRenderData>) -> View; + fn button( + &self, + control: ControlRenderData>, + data_signal: RwSignal, + ) -> View; fn checkbox( &self, control: ControlRenderData, @@ -85,8 +89,7 @@ pub trait FormStyle: Default + 'static { validation_state: Signal>, ) -> View; fn submit(&self, control: ControlRenderData) -> View; - // TODO: test custom component fn custom_component(&self, view: View) -> View; - fn group(&self, inner: View, style: Vec) -> View; + fn group(&self, group: ControlRenderData) -> View; fn spacer(&self, control: ControlRenderData) -> View; } diff --git a/src/validation_builder.rs b/src/validation_builder.rs index 43c4514..b70b254 100644 --- a/src/validation_builder.rs +++ b/src/validation_builder.rs @@ -1,6 +1,12 @@ use crate::{controls::ValidationFn, FormToolData}; 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 = dyn Fn(&str, &T) -> Result<(), String> + 'static; + /// A helper builder that allows you to specify a validation function /// declaritivly /// @@ -14,7 +20,7 @@ pub struct ValidationBuilder { /// The getter function for the field to validate. field_fn: Box &T + 'static>, /// The functions to be called when validating. - functions: Vec Result<(), String> + 'static>>, + functions: Vec>>, } impl ValidationBuilder {