Compare commits

...

2 Commits

Author SHA1 Message Date
857dcec00f third round of dev 2024-03-20 20:33:13 -05:00
80a967346c before removing VanityControl and Control 2024-03-17 14:25:06 -05:00
9 changed files with 402 additions and 139 deletions

View File

@ -1,6 +1,6 @@
use leptos::View; use leptos::View;
use super::{VanityControl, VanityControlBuilder, VanityControlData}; use super::{ControlRenderData, VanityControlBuilder, VanityControlData};
use crate::{ use crate::{
form::{FormBuilder, FormData}, form::{FormBuilder, FormData},
styles::FormStyle, styles::FormStyle,
@ -12,10 +12,7 @@ pub struct HeadingData {
} }
impl VanityControlData for HeadingData { impl VanityControlData for HeadingData {
fn build_control<FD: FormData, FS: FormStyle>( fn build_control<FS: FormStyle>(fs: &FS, control: ControlRenderData<FS, Self>) -> View {
fs: &FS,
control: VanityControl<FS, Self>,
) -> View {
fs.heading(control) fs.heading(control)
} }
} }
@ -23,7 +20,7 @@ impl VanityControlData for HeadingData {
impl<FD: FormData, FS: FormStyle> FormBuilder<FD, FS> { impl<FD: FormData, FS: FormStyle> FormBuilder<FD, FS> {
pub fn heading( pub fn heading(
self, self,
builder: impl Fn(VanityControlBuilder<FS, HeadingData>) -> VanityControl<FS, HeadingData>, builder: impl Fn(VanityControlBuilder<FS, HeadingData>) -> VanityControlBuilder<FS, HeadingData>,
) -> Self { ) -> Self {
self.new_vanity(builder) self.new_vanity(builder)
} }

View File

@ -1,5 +1,5 @@
use crate::{form::FormData, styles::FormStyle}; use crate::{form::FormData, styles::FormStyle};
use leptos::View; use leptos::{Signal, View};
pub mod heading; pub mod heading;
pub mod select; pub mod select;
@ -7,37 +7,50 @@ pub mod submit;
pub mod text_area; pub mod text_area;
pub mod text_input; pub mod text_input;
pub type ValidationFn<FD> = dyn Fn(&FD) -> Result<(), String> + 'static; pub trait ValidationFn<FD: FormData>: Fn(&FD) -> Result<(), String> + 'static {}
pub type ParseFn<FD, CReturnType> = dyn Fn(CReturnType, &mut FD) -> Result<(), String> + 'static; pub trait ParseFn<FD: FormData, C: ControlData>:
Fn(C::ReturnType, &mut FD) -> Result<(), String> + 'static
{
}
// implement the trait for all valid types
impl<FD, T> ValidationFn<FD> for T
where
FD: FormData,
T: Fn(&FD) -> Result<(), String> + 'static,
{
}
// implement the trait for all valid types
impl<FD, C, T> ParseFn<FD, C> for T
where
FD: FormData,
C: ControlData,
T: Fn(C::ReturnType, &mut FD) -> Result<(), String> + 'static,
{
}
pub trait VanityControlData: 'static { pub trait VanityControlData: 'static {
fn build_control<FD: FormData, FS: FormStyle>( fn build_control<FS: FormStyle>(fs: &FS, control: ControlRenderData<FS, Self>) -> View;
fs: &FS,
control: VanityControl<FS, Self>,
) -> View;
} }
pub trait ControlData: 'static { pub trait ControlData: 'static {
type ReturnType; type ReturnType: Clone;
// TODO: this should also return a getter for the data // TODO: this should also return a getter for the data
fn build_control<FD: FormData, FS: FormStyle>(fs: &FS, control: Control<FD, FS, Self>) -> View; fn build_control<FS: FormStyle>(
fs: &FS,
control: ControlRenderData<FS, Self>,
validation_state: Signal<Result<(), String>>,
) -> (View, Signal<Self::ReturnType>);
} }
pub struct VanityControl<FS: FormStyle + ?Sized, C: VanityControlData + ?Sized> { pub struct ControlRenderData<FS: FormStyle + ?Sized, C: ?Sized> {
pub data: Box<C>, pub data: Box<C>,
pub style: Vec<FS::StylingAttributes>, pub style: Vec<FS::StylingAttributes>,
} }
pub struct Control<FD: FormData + ?Sized, FS: FormStyle + ?Sized, C: ControlData + ?Sized> {
pub data: Box<C>,
pub parse_fn: Box<ParseFn<FD, C::ReturnType>>,
pub validation: Box<ValidationFn<FD>>,
pub style: Vec<FS::StylingAttributes>,
}
pub struct VanityControlBuilder<FS: FormStyle, C: VanityControlData> { pub struct VanityControlBuilder<FS: FormStyle, C: VanityControlData> {
style_attributes: Vec<FS::StylingAttributes>, pub(crate) style_attributes: Vec<FS::StylingAttributes>,
data: C, pub(crate) data: C,
} }
impl<FS: FormStyle, C: VanityControlData> VanityControlBuilder<FS, C> { impl<FS: FormStyle, C: VanityControlData> VanityControlBuilder<FS, C> {
@ -48,15 +61,8 @@ impl<FS: FormStyle, C: VanityControlData> VanityControlBuilder<FS, C> {
} }
} }
pub fn build(self) -> VanityControl<FS, C> { pub(crate) fn build(self) -> ControlRenderData<FS, C> {
self.into() ControlRenderData {
}
}
impl<FS: FormStyle, C: VanityControlData> Into<VanityControl<FS, C>>
for VanityControlBuilder<FS, C>
{
fn into(self) -> VanityControl<FS, C> {
VanityControl {
data: Box::new(self.data), data: Box::new(self.data),
style: self.style_attributes, style: self.style_attributes,
} }
@ -64,10 +70,10 @@ impl<FS: FormStyle, C: VanityControlData> Into<VanityControl<FS, C>>
} }
pub struct ControlBuilder<FD: FormData, FS: FormStyle, C: ControlData> { pub struct ControlBuilder<FD: FormData, FS: FormStyle, C: ControlData> {
parse_fn: Box<ParseFn<FD, C::ReturnType>>, pub(crate) parse_fn: Box<dyn ParseFn<FD, C>>,
validation_fn: Box<ValidationFn<FD>>, pub(crate) validation_fn: Box<dyn ValidationFn<FD>>,
style_attributes: Vec<FS::StylingAttributes>, pub(crate) style_attributes: Vec<FS::StylingAttributes>,
data: C, pub(crate) data: C,
} }
impl<FD: FormData, FS: FormStyle, C: ControlData> ControlBuilder<FD, FS, C> { impl<FD: FormData, FS: FormStyle, C: ControlData> ControlBuilder<FD, FS, C> {
@ -80,15 +86,25 @@ impl<FD: FormData, FS: FormStyle, C: ControlData> ControlBuilder<FD, FS, C> {
} }
} }
pub fn build(self) -> Control<FD, FS, C> { pub(crate) fn build(
self.into() self,
) -> (
ControlRenderData<FS, C>,
impl ParseFn<FD, C>,
impl ValidationFn<FD>,
) {
(
ControlRenderData {
data: Box::new(self.data),
style: self.style_attributes,
},
self.parse_fn,
self.validation_fn,
)
} }
pub fn parse_fn( pub fn parse_fn(mut self, parse_fn: impl ParseFn<FD, C>) -> Self {
mut self, self.parse_fn = Box::new(parse_fn) as Box<dyn ParseFn<FD, C>>;
parse_fn: impl Fn(C::ReturnType, &mut FD) -> Result<(), String> + 'static,
) -> Self {
self.parse_fn = Box::new(parse_fn) as Box<ParseFn<FD, C::ReturnType>>;
self self
} }
@ -96,7 +112,7 @@ impl<FD: FormData, FS: FormStyle, C: ControlData> ControlBuilder<FD, FS, C> {
mut self, mut self,
validation_fn: impl Fn(&FD) -> Result<(), String> + 'static, validation_fn: impl Fn(&FD) -> Result<(), String> + 'static,
) -> Self { ) -> Self {
self.validation_fn = Box::new(validation_fn) as Box<ValidationFn<FD>>; self.validation_fn = Box::new(validation_fn) as Box<dyn ValidationFn<FD>>;
self self
} }
@ -105,15 +121,3 @@ impl<FD: FormData, FS: FormStyle, C: ControlData> ControlBuilder<FD, FS, C> {
self self
} }
} }
impl<FD: FormData, FS: FormStyle, C: ControlData> Into<Control<FD, FS, C>>
for ControlBuilder<FD, FS, C>
{
fn into(self) -> Control<FD, FS, C> {
Control {
data: Box::new(self.data),
style: self.style_attributes,
parse_fn: self.parse_fn,
validation: self.validation_fn,
}
}
}

View File

@ -1,6 +1,6 @@
use leptos::View; use leptos::{Signal, View};
use super::{Control, ControlBuilder, ControlData}; use super::{ControlBuilder, ControlData, ControlRenderData};
use crate::{ use crate::{
form::{FormBuilder, FormData}, form::{FormBuilder, FormData},
styles::FormStyle, styles::FormStyle,
@ -15,15 +15,19 @@ pub struct SelectData {
impl ControlData for SelectData { impl ControlData for SelectData {
type ReturnType = String; type ReturnType = String;
fn build_control<FD: FormData, FS: FormStyle>(fs: &FS, control: Control<FD, FS, Self>) -> View { fn build_control<FS: FormStyle>(
fs.select(control) fs: &FS,
control: ControlRenderData<FS, Self>,
validation_state: Signal<Result<(), String>>,
) -> (View, Signal<Self::ReturnType>) {
fs.select(control, validation_state)
} }
} }
impl<FD: FormData, FS: FormStyle> FormBuilder<FD, FS> { impl<FD: FormData, FS: FormStyle> FormBuilder<FD, FS> {
pub fn select( pub fn select(
self, self,
builder: impl Fn(ControlBuilder<FD, FS, SelectData>) -> Control<FD, FS, SelectData>, builder: impl Fn(ControlBuilder<FD, FS, SelectData>) -> ControlBuilder<FD, FS, SelectData>,
) -> Self { ) -> Self {
self.new_control(builder) self.new_control(builder)
} }

View File

@ -1,6 +1,6 @@
use leptos::View; use leptos::View;
use super::{VanityControl, VanityControlBuilder, VanityControlData}; use super::{ControlRenderData, VanityControlBuilder, VanityControlData};
use crate::{ use crate::{
form::{FormBuilder, FormData}, form::{FormBuilder, FormData},
styles::FormStyle, styles::FormStyle,
@ -12,10 +12,7 @@ pub struct SubmitData {
} }
impl VanityControlData for SubmitData { impl VanityControlData for SubmitData {
fn build_control<FD: FormData, FS: FormStyle>( fn build_control<FS: FormStyle>(fs: &FS, control: ControlRenderData<FS, Self>) -> View {
fs: &FS,
control: VanityControl<FS, Self>,
) -> View {
fs.submit(control) fs.submit(control)
} }
} }
@ -23,7 +20,7 @@ impl VanityControlData for SubmitData {
impl<FD: FormData, FS: FormStyle> FormBuilder<FD, FS> { impl<FD: FormData, FS: FormStyle> FormBuilder<FD, FS> {
pub fn submit( pub fn submit(
self, self,
builder: impl Fn(VanityControlBuilder<FS, SubmitData>) -> VanityControl<FS, SubmitData>, builder: impl Fn(VanityControlBuilder<FS, SubmitData>) -> VanityControlBuilder<FS, SubmitData>,
) -> Self { ) -> Self {
self.new_vanity(builder) self.new_vanity(builder)
} }

View File

@ -1,10 +1,9 @@
use leptos::View; use super::{ControlBuilder, ControlData, ControlRenderData};
use super::{Control, ControlBuilder, ControlData};
use crate::{ use crate::{
form::{FormBuilder, FormData}, form::{FormBuilder, FormData},
styles::FormStyle, styles::FormStyle,
}; };
use leptos::{Signal, View};
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct TextAreaData { pub struct TextAreaData {
@ -15,15 +14,19 @@ pub struct TextAreaData {
impl ControlData for TextAreaData { impl ControlData for TextAreaData {
type ReturnType = String; type ReturnType = String;
fn build_control<FD: FormData, FS: FormStyle>(fs: &FS, control: Control<FD, FS, Self>) -> View { fn build_control<FS: FormStyle>(
fs.text_area(control) fs: &FS,
control: ControlRenderData<FS, Self>,
validation_state: Signal<Result<(), String>>,
) -> (View, Signal<Self::ReturnType>) {
fs.text_area(control, validation_state)
} }
} }
impl<FD: FormData, FS: FormStyle> FormBuilder<FD, FS> { impl<FD: FormData, FS: FormStyle> FormBuilder<FD, FS> {
pub fn text_area( pub fn text_area(
self, self,
builder: impl Fn(ControlBuilder<FD, FS, TextAreaData>) -> Control<FD, FS, TextAreaData>, builder: impl Fn(ControlBuilder<FD, FS, TextAreaData>) -> ControlBuilder<FD, FS, TextAreaData>,
) -> Self { ) -> Self {
self.new_control(builder) self.new_control(builder)
} }

View File

@ -1,6 +1,6 @@
use leptos::View; use leptos::{Signal, View};
use super::{Control, ControlBuilder, ControlData}; use super::{ControlBuilder, ControlData, ControlRenderData};
use crate::{ use crate::{
form::{FormBuilder, FormData}, form::{FormBuilder, FormData},
styles::FormStyle, styles::FormStyle,
@ -30,15 +30,19 @@ impl Default for TextInputData {
impl ControlData for TextInputData { impl ControlData for TextInputData {
type ReturnType = String; type ReturnType = String;
fn build_control<FD: FormData, FS: FormStyle>(fs: &FS, control: Control<FD, FS, Self>) -> View { fn build_control<FS: FormStyle>(
fs.text_input(control) fs: &FS,
control: ControlRenderData<FS, Self>,
validation_state: Signal<Result<(), String>>,
) -> (View, Signal<Self::ReturnType>) {
fs.text_input(control, validation_state)
} }
} }
impl<FD: FormData, FS: FormStyle> FormBuilder<FD, FS> { impl<FD: FormData, FS: FormStyle> FormBuilder<FD, FS> {
pub fn text_input( pub fn text_input(
self, self,
builder: impl Fn(ControlBuilder<FD, FS, TextInputData>) -> Control<FD, FS, TextInputData>, builder: impl Fn(ControlBuilder<FD, FS, TextInputData>) -> ControlBuilder<FD, FS, TextInputData>,
) -> Self { ) -> Self {
self.new_control(builder) self.new_control(builder)
} }

View File

@ -1,33 +1,143 @@
use crate::{ use crate::{
controls::{ controls::{
Control, ControlBuilder, ControlData, ValidationFn, VanityControl, VanityControlBuilder, ControlBuilder, ControlData, ValidationFn, VanityControlBuilder, VanityControlData,
VanityControlData,
}, },
styles::FormStyle, styles::FormStyle,
}; };
use leptos::{IntoView, View}; use leptos::{
use std::marker::PhantomData; create_effect, create_signal, IntoView, ReadSignal, SignalGet, SignalSet, SignalUpdate, View,
WriteSignal,
};
pub struct Validator<FD: FormData> {
validations: Vec<Box<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: ReadSignal<FD>,
validations: Vec<Box<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) -> (ReadSignal<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_get: ReadSignal<FD>,
fd_set: WriteSignal<FD>,
fs: FS,
validations: Vec<Box<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<Box<dyn ValidationFn<FD>>>,
},
}
pub struct FormBuilder<FD: FormData, FS: FormStyle> { pub struct FormBuilder<FD: FormData, FS: FormStyle> {
_fd: PhantomData<FD>, inner: FormBuilderInner<FD, FS>,
fs: FS,
validations: Vec<Box<ValidationFn<FD>>>,
views: Vec<View>,
} }
impl<FD: FormData, FS: FormStyle> FormBuilder<FD, FS> { impl<FD: FormData, FS: FormStyle> FormBuilder<FD, FS> {
pub fn new(form_style: FS) -> 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_get, fd_set) = create_signal(FD::default());
FormBuilder { FormBuilder {
_fd: PhantomData::default(), inner: FormBuilderInner::FullBuilder(FullFormBuilder {
fd_get,
fd_set,
fs: form_style, fs: form_style,
validations: Vec::new(), validations: Vec::new(),
views: Vec::new(), views: Vec::new(),
}),
}
}
fn new_full_builder_with(starting_data: FD, form_style: FS) -> FormBuilder<FD, FS> {
let (fd_get, fd_set) = create_signal(starting_data);
FormBuilder {
inner: FormBuilderInner::FullBuilder(FullFormBuilder {
fd_get,
fd_set,
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>( pub(crate) fn new_vanity<C: VanityControlData + Default>(
mut self, mut self,
builder: impl Fn(VanityControlBuilder<FS, C>) -> VanityControl<FS, C>, builder: impl Fn(VanityControlBuilder<FS, C>) -> VanityControlBuilder<FS, C>,
) -> Self { ) -> Self {
let vanity_builder = VanityControlBuilder::new(C::default()); let vanity_builder = VanityControlBuilder::new(C::default());
let control = builder(vanity_builder); let control = builder(vanity_builder);
@ -37,7 +147,7 @@ impl<FD: FormData, FS: FormStyle> FormBuilder<FD, FS> {
pub(crate) fn new_control<C: ControlData + Default>( pub(crate) fn new_control<C: ControlData + Default>(
mut self, mut self,
builder: impl Fn(ControlBuilder<FD, FS, C>) -> Control<FD, FS, C>, builder: impl Fn(ControlBuilder<FD, FS, C>) -> ControlBuilder<FD, FS, C>,
) -> Self { ) -> Self {
let control_builder = ControlBuilder::new(C::default()); let control_builder = ControlBuilder::new(C::default());
let control = builder(control_builder); let control = builder(control_builder);
@ -45,24 +155,102 @@ impl<FD: FormData, FS: FormStyle> FormBuilder<FD, FS> {
self self
} }
fn add_vanity<C: VanityControlData>(&mut self, vanity_control: VanityControl<FS, C>) { fn add_vanity<C: VanityControlData>(&mut self, vanity_control: VanityControlBuilder<FS, C>) {
let view = VanityControlData::build_control::<FD, FS>(&self.fs, vanity_control); let full_builder = match &mut self.inner {
self.views.push(view); FormBuilderInner::ValidationBuilder { validations: _ } => return,
FormBuilderInner::FullBuilder(full_builder) => full_builder,
};
let render_data = vanity_control.build();
let view = VanityControlData::build_control(&full_builder.fs, render_data);
full_builder.views.push(view);
} }
fn add_control<C: ControlData>(&mut self, control: Control<FD, FS, C>) { fn add_control<C: ControlData>(&mut self, control: ControlBuilder<FD, FS, C>) {
let view = ControlData::build_control(&self.fs, control); let full_builder = match &mut self.inner {
self.views.push(view); FormBuilderInner::ValidationBuilder { validations } => {
validations.push(control.validation_fn);
return;
}
FormBuilderInner::FullBuilder(full_builder) => full_builder,
};
let (render_data, parse_fn, validation_fn) = control.build();
let (validation_signal, validation_signal_set) = create_signal(Ok(()));
let (view, control_value) =
ControlData::build_control(&full_builder.fs, render_data, validation_signal.into());
// TODO: add a signal that triggers on submit to refresh the validation on_submit
let fd_setter = full_builder.fd_set;
create_effect(move |last_value| {
let control_value = control_value.get();
let mut validation_result = Ok(());
fd_setter.update(|v| {
validation_result = (parse_fn)(control_value, v).and_then(|_| (validation_fn)(v));
});
// TODO: or this happened on a submit
if last_value.is_some_and(|last_value| last_value != validation_result) {
validation_signal_set.set(validation_result.clone());
}
validation_result
});
full_builder.views.push(view);
} }
// TODO: this should return a Form object fn build(self) -> Form<FD> {
// The Form should have `form_view()`, and `validate(&FD)` functions. match self.inner {
pub fn build(self) -> View { FormBuilderInner::FullBuilder(full_builder) => Form {
self.views.into_view() fd: full_builder.fd_get,
validations: full_builder.validations,
view: full_builder.views.into_view(),
},
FormBuilderInner::ValidationBuilder { validations } => Form {
fd: create_signal(FD::default()).0,
validations,
view: ().into_view(),
},
}
}
fn validator(self) -> Validator<FD> {
match self.inner {
FormBuilderInner::FullBuilder(full_builder) => Validator {
validations: full_builder.validations,
},
FormBuilderInner::ValidationBuilder { validations } => Validator { validations },
}
} }
} }
pub trait FormData: Default { /// A trait allowing a form to be built around its containing data.
// TODO: this should return a Form Object ///
fn create_form() -> View; /// 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.
///
/// 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>;
/// Gets the [`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()
}
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()
}
fn get_validator() -> Validator<Self> {
let builder = FormBuilder::new_validation_builder();
let builder = Self::build_form(builder);
builder.validator()
}
} }

View File

@ -2,24 +2,36 @@ mod tw_grid;
pub use tw_grid::{TailwindGridFormStyle, TailwindGridStylingAttributes}; pub use tw_grid::{TailwindGridFormStyle, TailwindGridStylingAttributes};
use crate::{ use crate::controls::{
controls::{
heading::HeadingData, select::SelectData, submit::SubmitData, text_area::TextAreaData, heading::HeadingData, select::SelectData, submit::SubmitData, text_area::TextAreaData,
text_input::TextInputData, Control, VanityControl, text_input::TextInputData, ControlData, ControlRenderData,
},
form::FormData,
}; };
use leptos::View; use leptos::{Signal, View};
pub trait FormStyle: 'static { pub trait FormStyle: 'static {
type StylingAttributes; type StylingAttributes;
// TODO: add form frame // TODO: add form frame
fn heading(&self, control: VanityControl<Self, HeadingData>) -> View; // TODO: perhaps we don't want to send the full control type anymore.
fn text_input<FD: FormData>(&self, control: Control<FD, Self, TextInputData>) -> View; // as the rendering shouldn't depend on parse or validate anymore.
fn select<FD: FormData>(&self, control: Control<FD, Self, SelectData>) -> View; fn heading(&self, control: ControlRenderData<Self, HeadingData>) -> View;
fn submit(&self, control: VanityControl<Self, SubmitData>) -> View; fn text_input(
fn text_area<FD: FormData>(&self, control: Control<FD, Self, TextAreaData>) -> View; &self,
control: ControlRenderData<Self, TextInputData>,
validation_state: Signal<Result<(), String>>,
) -> (View, Signal<<TextInputData as ControlData>::ReturnType>);
fn select(
&self,
control: ControlRenderData<Self, SelectData>,
validation_state: Signal<Result<(), String>>,
) -> (View, Signal<<SelectData as ControlData>::ReturnType>);
fn submit(&self, control: ControlRenderData<Self, SubmitData>) -> View;
fn text_area(
&self,
control: ControlRenderData<Self, TextAreaData>,
validation_state: Signal<Result<(), String>>,
) -> (View, Signal<<TextAreaData as ControlData>::ReturnType>);
// TODO: test custom component
fn custom_component(&self, view: View) -> View; fn custom_component(&self, view: View) -> View;
// TODO: add group // TODO: add group
} }

View File

@ -1,13 +1,9 @@
use super::FormStyle; use super::FormStyle;
use crate::{ use crate::controls::{
controls::{
heading::HeadingData, select::SelectData, submit::SubmitData, text_area::TextAreaData, heading::HeadingData, select::SelectData, submit::SubmitData, text_area::TextAreaData,
text_input::TextInputData, Control, VanityControl, text_input::TextInputData, ControlRenderData,
},
form::FormData,
}; };
use leptos::CollectView; use leptos::*;
use leptos::{view, IntoView, View};
pub enum TailwindGridStylingAttributes { pub enum TailwindGridStylingAttributes {
Width(u32), Width(u32),
@ -18,7 +14,7 @@ pub struct TailwindGridFormStyle;
impl FormStyle for TailwindGridFormStyle { impl FormStyle for TailwindGridFormStyle {
type StylingAttributes = TailwindGridStylingAttributes; type StylingAttributes = TailwindGridStylingAttributes;
fn heading(&self, control: VanityControl<Self, HeadingData>) -> View { fn heading(&self, control: ControlRenderData<Self, HeadingData>) -> View {
view! { view! {
<h2 class="text-xl py-2 text-center font-bold text-gray-700 border-b-2 border-gray-800/60 mb-8"> <h2 class="text-xl py-2 text-center font-bold text-gray-700 border-b-2 border-gray-800/60 mb-8">
{&control.data.title} {&control.data.title}
@ -27,9 +23,17 @@ impl FormStyle for TailwindGridFormStyle {
.into_view() .into_view()
} }
fn text_input<FD: FormData>(&self, control: Control<FD, Self, TextInputData>) -> View { fn text_input(
view! { &self,
control: ControlRenderData<Self, TextInputData>,
validation_state: Signal<Result<(), String>>,
) -> (View, Signal<String>) {
let (read, write) = create_signal(String::new());
leptos::logging::log!("Rendering text input");
let view = view! {
<div> <div>
{move || format!("{:?}", validation_state.get())}
<label <label
for={&control.data.name} for={&control.data.name}
class="block uppercase tracking-wide text-left text-gray-700 text-md font-bold ml-2 mb-1" class="block uppercase tracking-wide text-left text-gray-700 text-md font-bold ml-2 mb-1"
@ -43,25 +47,61 @@ impl FormStyle for TailwindGridFormStyle {
name=control.data.name name=control.data.name
placeholder=control.data.placeholder placeholder=control.data.placeholder
value=control.data.initial_text value=control.data.initial_text
on:change=move |ev| {
write.set(event_target_value(&ev));
}
class="block w-full bg-gray-100 border-2 border-gray-300 text-gray-700 py-2 px-4 rounded-lg leading-tight focus:outline-none focus:bg-white focus:border-sky-400" class="block w-full bg-gray-100 border-2 border-gray-300 text-gray-700 py-2 px-4 rounded-lg leading-tight focus:outline-none focus:bg-white focus:border-sky-400"
/> />
</div> </div>
}.into_view() }.into_view();
(view, read.into())
} }
fn select<FD: FormData>(&self, control: Control<FD, Self, SelectData>) -> View { fn select(
&self,
control: ControlRenderData<Self, SelectData>,
validation_state: Signal<Result<(), String>>,
) -> (View, Signal<String>) {
let (read, write) = create_signal(String::new());
let options_view = control
.data
.options
.into_iter()
.map(|value| {
// let value = value;
let cloned_value = value.clone();
view! { view! {
<option
value={value}
selected=move || read.get() == *cloned_value
>
*value
</option>
}
})
.collect_view();
let view = view! {
<div>
{move || format!("{:?}", validation_state.get())}
<select <select
id=&control.data.name id=&control.data.name
name=control.data.name name=control.data.name
class="block w-full bg-gray-100 border-2 border-gray-300 text-gray-700 py-2 px-4 rounded-lg leading-tight focus:outline-none focus:bg-white focus:border-sky-400" class="block w-full bg-gray-100 border-2 border-gray-300 text-gray-700 py-2 px-4 rounded-lg leading-tight focus:outline-none focus:bg-white focus:border-sky-400"
on:change=move |ev| {
write.set(event_target_value(&ev));
}
> >
{control.data.options.iter().map(|option| view!{<option>{option}</option>}).collect_view()} {options_view}
</select> </select>
}.into_view() </div>
}.into_view();
(view, read.into())
} }
fn submit(&self, control: VanityControl<Self, SubmitData>) -> View { fn submit(&self, control: ControlRenderData<Self, SubmitData>) -> View {
view! { view! {
<input <input
type="submit" type="submit"
@ -72,15 +112,29 @@ impl FormStyle for TailwindGridFormStyle {
.into_view() .into_view()
} }
fn text_area<FD: FormData>(&self, control: Control<FD, Self, TextAreaData>) -> View { fn text_area(
view! { &self,
control: ControlRenderData<Self, TextAreaData>,
validation_state: Signal<Result<(), String>>,
) -> (View, Signal<String>) {
let (read, write) = create_signal(String::new());
let view = view! {
<div>
{move || format!("{:?}", validation_state.get())}
<textarea <textarea
id=&control.data.name id=&control.data.name
name=control.data.name name=control.data.name
placeholder=control.data.placeholder placeholder=control.data.placeholder
class="block w-full bg-gray-100 border-2 border-gray-300 text-gray-700 py-2 px-4 rounded-lg leading-tight focus:outline-none focus:bg-white focus:border-sky-400" class="block w-full bg-gray-100 border-2 border-gray-300 text-gray-700 py-2 px-4 rounded-lg leading-tight focus:outline-none focus:bg-white focus:border-sky-400"
on:change=move |ev| {
write.set(event_target_value(&ev));
}
/> />
}.into_view() </div>
}.into_view();
(view, read.into())
} }
fn custom_component(&self, view: View) -> View { fn custom_component(&self, view: View) -> View {