Compare commits

..

No commits in common. "edc9c4d371af4dc9bc820d6438f7b8fda847c29e" and "fb47d553e79b4f6e76993b3c4ffdbb817c2218f0" have entirely different histories.

5 changed files with 140 additions and 211 deletions

View File

@ -23,13 +23,6 @@
border-bottom: 2px solid #374151; border-bottom: 2px solid #374151;
margin-bottom: 2rem; margin-bottom: 2rem;
grid-column: span 12; grid-column: span 12;
display: grid;
}
.form_group {
background-color: #0295f744;
border-radius: 25px;
padding: 20px;
} }
.form_label { .form_label {
@ -86,19 +79,12 @@
color: #fff; color: #fff;
font-weight: bold; font-weight: bold;
cursor: pointer; cursor: pointer;
margin: 0 auto auto auto; margin: 0 auto;
padding: 0.75rem 1.25rem; padding: 0.75rem 1.25rem;
font-size: 1rem; font-size: 1rem;
appearance: none; appearance: none;
min-height: 40px; min-height: 40px;
} }
.form_submit {
@extend .form_button;
@extend .col-span-full;
margin: 0 auto;
}
.form_submit:hover { .form_submit:hover {
background-color: #005fb3; background-color: #005fb3;
} }

View File

@ -1,38 +1,36 @@
use super::ValidationCb; use super::{ControlRenderData, VanityControlBuilder, VanityControlData};
use crate::{form::FormToolData, form_builder::FormBuilder, styles::FormStyle}; use crate::{form::FormToolData, form_builder::FormBuilder, styles::FormStyle};
use leptos::{CollectView, RwSignal}; use leptos::{Signal, View};
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct GroupData {
pub(crate) title: Option<String>,
}
impl VanityControlData for GroupData {
fn build_control<FS: FormStyle>(
fs: &FS,
control: ControlRenderData<FS, Self>,
_value_getter: Option<Signal<String>>,
) -> View {
fs.group(control)
}
}
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(
let mut group_builder = FormBuilder::new_group(self.fd, self.fs); self,
group_builder = builder(group_builder); builder: impl Fn(
self.fs = group_builder.fs; // take the style back VanityControlBuilder<FD, FS, GroupData>,
) -> VanityControlBuilder<FD, FS, GroupData>,
for validation in group_builder.validations { ) -> Self {
self.validations.push(validation); self.new_vanity(builder)
} }
}
let render_fn = move |fs: &FS, fd: RwSignal<FD>| { impl<FD: FormToolData, FS: FormStyle> VanityControlBuilder<FD, FS, GroupData> {
let (views, validation_cbs): (Vec<_>, Vec<_>) = group_builder pub fn title(mut self, title: impl ToString) -> Self {
.render_fns self.data.title = Some(title.to_string());
.into_iter()
.map(|r_fn| r_fn(&fs, fd))
.unzip();
let view = fs.group(views.collect_view(), group_builder.styles);
let validation_cb = move || {
let mut success = true;
for validation in validation_cbs.iter().flatten() {
if !validation() {
success = false;
}
}
success
};
(view, Some(Box::new(validation_cb) as Box<dyn ValidationCb>))
};
self.render_fns.push(Box::new(render_fn));
self self
} }
} }

View File

@ -23,13 +23,11 @@ pub struct FormBuilder<FD: FormToolData, FS: FormStyle> {
/// The [`ToolFormData`] signal. /// The [`ToolFormData`] signal.
pub(crate) fd: RwSignal<FD>, pub(crate) fd: RwSignal<FD>,
/// The [`FormStyle`]. /// The [`FormStyle`].
pub(crate) fs: FS, fs: FS,
/// The list of [`ValidationFn`]s. /// The list of [`ValidationFn`]s.
pub(crate) validations: Vec<Rc<dyn ValidationFn<FD>>>, validations: Vec<Rc<dyn ValidationFn<FD>>>,
/// The list of functions that will render the form. /// The list of functions that will render the form.
pub(crate) render_fns: Vec<Box<dyn RenderFn<FS, FD>>>, render_fns: Vec<Box<dyn RenderFn<FS, FD>>>,
/// The list of styling attributes applied on the form level
pub(crate) styles: Vec<FS::StylingAttributes>,
} }
impl<FD: FormToolData, FS: FormStyle> FormBuilder<FD, FS> { impl<FD: FormToolData, FS: FormStyle> FormBuilder<FD, FS> {
@ -41,25 +39,9 @@ impl<FD: FormToolData, FS: FormStyle> FormBuilder<FD, FS> {
fs: form_style, fs: form_style,
validations: Vec::new(), validations: Vec::new(),
render_fns: 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(),
render_fns: Vec::new(),
styles: Vec::new(),
}
}
pub fn style(mut self, style: FS::StylingAttributes) -> Self {
self.styles.push(style);
self
}
pub(crate) fn new_vanity<C: VanityControlData + Default>( pub(crate) fn new_vanity<C: VanityControlData + Default>(
mut self, mut self,
builder: impl Fn(VanityControlBuilder<FD, FS, C>) -> VanityControlBuilder<FD, FS, C>, builder: impl Fn(VanityControlBuilder<FD, FS, C>) -> VanityControlBuilder<FD, FS, C>,
@ -137,7 +119,7 @@ impl<FD: FormToolData, FS: FormStyle> FormBuilder<FD, FS> {
self.render_fns.push(Box::new(render_fn)); self.render_fns.push(Box::new(render_fn));
} }
fn build_control_view<C: ControlData, FDT: 'static>( fn build_control_view<C: ControlData, FDT: Clone + PartialEq + 'static>(
fd: RwSignal<FD>, fd: RwSignal<FD>,
fs: &FS, fs: &FS,
getter: Rc<dyn FieldGetter<FD, FDT>>, getter: Rc<dyn FieldGetter<FD, FDT>>,
@ -148,21 +130,11 @@ impl<FD: FormToolData, FS: FormStyle> FormBuilder<FD, FS> {
render_data: crate::controls::ControlRenderData<FS, C>, render_data: crate::controls::ControlRenderData<FS, C>,
) -> (View, Box<dyn ValidationCb>) { ) -> (View, Box<dyn ValidationCb>) {
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 value_getter = move || { let value_getter = move || {
let fd = fd.get(); let getter = getter.clone();
// memoize so that updating one field doesn't cause a re-render for all fields
// rerun validation if it is failing let field = create_memo(move |_| getter(fd.get()));
if validation_signal.get_untracked().is_err() { unparse_fn(field.get())
if let Some(ref validation_fn) = validation_fn_clone {
let validation_result = validation_fn(&fd);
// if validation succeeds this time, resolve the validation error
if validation_result.is_ok() {
validation_signal_set.set(Ok(()));
}
}
}
unparse_fn(getter(fd))
}; };
let value_getter = value_getter.into_signal(); let value_getter = value_getter.into_signal();
@ -181,31 +153,7 @@ impl<FD: FormToolData, FS: FormStyle> FormBuilder<FD, FS> {
}; };
let validation_cb = Box::new(validation_cb); let validation_cb = Box::new(validation_cb);
let value_setter = Self::create_value_setter( let validation_cb2 = validation_cb.clone();
validation_cb.clone(),
validation_signal_set,
parse_fn,
setter,
fd,
);
let view = C::build_control(
fs,
render_data,
value_getter,
value_setter,
validation_signal.into(),
);
(view, validation_cb)
}
fn create_value_setter<CRT: 'static, FDT: 'static>(
validation_cb: Box<dyn Fn() -> bool + 'static>,
validation_signal_set: WriteSignal<Result<(), String>>,
parse_fn: Box<dyn ParseFn<CRT, FDT>>,
setter: Rc<dyn FieldSetter<FD, FDT>>,
fd: RwSignal<FD>,
) -> Box<dyn Fn(CRT) + 'static> {
let value_setter = move |value| { let value_setter = move |value| {
let parsed = match parse_fn(value) { let parsed = match parse_fn(value) {
Ok(p) => { Ok(p) => {
@ -224,9 +172,18 @@ impl<FD: FormToolData, FS: FormStyle> FormBuilder<FD, FS> {
}); });
// run validation // run validation
(validation_cb)(); (validation_cb2)();
}; };
Box::new(value_setter) 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>( pub(crate) fn build_action_form<ServFn>(
@ -244,7 +201,7 @@ impl<FD: FormToolData, FS: FormStyle> FormBuilder<FD, FS> {
.map(|r_fn| r_fn(&self.fs, self.fd)) .map(|r_fn| r_fn(&self.fs, self.fd))
.unzip(); .unzip();
let elements = self.fs.form_frame(views.into_view(), self.styles); let elements = self.fs.form_frame(views.into_view());
let on_submit = move |ev: SubmitEvent| { let on_submit = move |ev: SubmitEvent| {
let mut failed = false; let mut failed = false;
@ -278,7 +235,7 @@ impl<FD: FormToolData, FS: FormStyle> FormBuilder<FD, FS> {
.map(|r_fn| r_fn(&self.fs, self.fd)) .map(|r_fn| r_fn(&self.fs, self.fd))
.unzip(); .unzip();
let elements = self.fs.form_frame(views.into_view(), self.styles); let elements = self.fs.form_frame(views.into_view());
let on_submit = move |ev: SubmitEvent| { let on_submit = move |ev: SubmitEvent| {
let mut failed = false; let mut failed = false;

View File

@ -18,7 +18,7 @@ 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, children: View) -> View {
view! { <div class="form_grid">{children}</div> }.into_view() view! { <div class="form_grid">{children}</div> }.into_view()
} }
@ -79,11 +79,12 @@ impl FormStyle for GridFormStyle {
.options .options
.into_iter() .into_iter()
.map(|value| { .map(|value| {
// let value = value;
let cloned_value = value.clone(); let cloned_value = value.clone();
view! { view! {
<option <option
value=value.clone() value=value.clone()
selected=move || {value_getter.get() == *cloned_value} selected=move || value_getter.get() == *cloned_value
> >
{value.clone()} {value.clone()}
</option> </option>
@ -99,7 +100,7 @@ impl FormStyle for GridFormStyle {
id=&control.data.name id=&control.data.name
name=control.data.name name=control.data.name
class="form_input" class="form_input"
on:input=move |ev| { on:change=move |ev| {
value_setter(event_target_value(&ev)); value_setter(event_target_value(&ev));
} }
> >
@ -113,7 +114,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"/> <input type="submit" value=control.data.text class="col-span-full form_button"/>
} }
.into_view() .into_view()
} }
@ -352,6 +353,10 @@ impl FormStyle for GridFormStyle {
.into_view() .into_view()
} }
fn group(&self, _control: ControlRenderData<Self, crate::controls::group::GroupData>) -> View {
todo!()
}
fn button<FD: crate::FormToolData>( fn button<FD: crate::FormToolData>(
&self, &self,
control: ControlRenderData<Self, crate::controls::button::ButtonData<FD>>, control: ControlRenderData<Self, crate::controls::button::ButtonData<FD>>,
@ -372,27 +377,10 @@ impl FormStyle for GridFormStyle {
}; };
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) class="form_button">
{control.data.text} {control.data.text}
</button> </button>
} }
.into_view() .into_view()
} }
// TODO: change this and form frame to use ControlRenderData
fn group(&self, inner: View, styles: Vec<Self::StylingAttributes>) -> View {
let mut width = 12;
for style in styles {
match style {
GridFormStylingAttributes::Width(w) => width = w,
}
}
view! {
<div class="form_group form_grid" style:grid-column=format!("span {}", width)>
{inner}
</div>
}
.into_view()
}
} }

View File

@ -1,17 +1,17 @@
mod grid_form; mod grid_form;
pub use grid_form::{GridFormStyle, GridFormStylingAttributes};
use crate::{ use crate::{
controls::{ controls::{
button::ButtonData, checkbox::CheckboxData, heading::HeadingData, hidden::HiddenData, button::ButtonData, checkbox::CheckboxData, group::GroupData, heading::HeadingData,
output::OutputData, radio_buttons::RadioButtonsData, select::SelectData, hidden::HiddenData, output::OutputData, radio_buttons::RadioButtonsData,
slider::SliderData, stepper::StepperData, submit::SubmitData, text_area::TextAreaData, select::SelectData, slider::SliderData, stepper::StepperData, submit::SubmitData,
text_input::TextInputData, ControlData, ControlRenderData, text_area::TextAreaData, text_input::TextInputData, ControlData, ControlRenderData,
}, },
FormToolData, FormToolData,
}; };
use leptos::{Signal, View}; use leptos::{Signal, View};
pub use grid_form::{GridFormStyle, GridFormStylingAttributes};
pub trait FormStyle: Default + 'static { pub trait FormStyle: Default + 'static {
type StylingAttributes; type StylingAttributes;
@ -22,7 +22,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, styles: Vec<Self::StylingAttributes>) -> View; fn form_frame(&self, children: View) -> View;
fn heading(&self, control: ControlRenderData<Self, HeadingData>) -> View; fn heading(&self, control: ControlRenderData<Self, HeadingData>) -> View;
fn hidden( fn hidden(
&self, &self,
@ -86,6 +86,6 @@ pub trait FormStyle: Default + 'static {
fn submit(&self, control: ControlRenderData<Self, SubmitData>) -> View; fn submit(&self, control: ControlRenderData<Self, SubmitData>) -> View;
// TODO: test custom component // TODO: test custom component
fn custom_component(&self, view: View) -> View; fn custom_component(&self, view: View) -> View;
// TODO: test group // TODO: add group
fn group(&self, inner: View, styles: Vec<Self::StylingAttributes>) -> View; fn group(&self, control: ControlRenderData<Self, GroupData>) -> View;
} }