impl groups

This commit is contained in:
Mitchell Marino 2024-06-07 14:21:14 -05:00
parent fb47d553e7
commit 31b24a23a1
5 changed files with 182 additions and 134 deletions

View File

@ -23,6 +23,14 @@
border-bottom: 2px solid #374151;
margin-bottom: 2rem;
grid-column: span 12;
display: grid;
}
.form_group {
background-color: #0295f744;
border-radius: 25px;
padding: 20px;
grid-column: 1 / -1;
}
.form_label {

View File

@ -1,36 +1,38 @@
use super::{ControlRenderData, VanityControlBuilder, VanityControlData};
use super::ValidationCb;
use crate::{form::FormToolData, form_builder::FormBuilder, styles::FormStyle};
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)
}
}
use leptos::{CollectView, RwSignal};
impl<FD: FormToolData, FS: FormStyle> FormBuilder<FD, FS> {
pub fn group(
self,
builder: impl Fn(
VanityControlBuilder<FD, FS, GroupData>,
) -> VanityControlBuilder<FD, FS, GroupData>,
) -> Self {
self.new_vanity(builder)
}
}
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);
group_builder = builder(group_builder);
self.fs = group_builder.fs; // take the style back
impl<FD: FormToolData, FS: FormStyle> VanityControlBuilder<FD, FS, GroupData> {
pub fn title(mut self, title: impl ToString) -> Self {
self.data.title = Some(title.to_string());
for validation in group_builder.validations {
self.validations.push(validation);
}
let render_fn = move |fs: &FS, fd: RwSignal<FD>| {
let (views, validation_cbs): (Vec<_>, Vec<_>) = group_builder
.render_fns
.into_iter()
.map(|r_fn| r_fn(&fs, fd))
.unzip();
let view = fs.group(views.collect_view());
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
}
}

View File

@ -23,11 +23,11 @@ pub struct FormBuilder<FD: FormToolData, FS: FormStyle> {
/// The [`ToolFormData`] signal.
pub(crate) fd: RwSignal<FD>,
/// The [`FormStyle`].
fs: FS,
pub(crate) fs: FS,
/// The list of [`ValidationFn`]s.
validations: Vec<Rc<dyn ValidationFn<FD>>>,
pub(crate) validations: Vec<Rc<dyn ValidationFn<FD>>>,
/// The list of functions that will render the form.
render_fns: Vec<Box<dyn RenderFn<FS, FD>>>,
pub(crate) render_fns: Vec<Box<dyn RenderFn<FS, FD>>>,
}
impl<FD: FormToolData, FS: FormStyle> FormBuilder<FD, FS> {
@ -42,6 +42,15 @@ impl<FD: FormToolData, FS: FormStyle> FormBuilder<FD, FS> {
}
}
pub(crate) fn new_group(fd: RwSignal<FD>, fs: FS) -> FormBuilder<FD, FS> {
FormBuilder {
fd,
fs,
validations: Vec::new(),
render_fns: Vec::new(),
}
}
pub(crate) fn new_vanity<C: VanityControlData + Default>(
mut self,
builder: impl Fn(VanityControlBuilder<FD, FS, C>) -> VanityControlBuilder<FD, FS, C>,
@ -119,7 +128,7 @@ impl<FD: FormToolData, FS: FormStyle> FormBuilder<FD, FS> {
self.render_fns.push(Box::new(render_fn));
}
fn build_control_view<C: ControlData, FDT: Clone + PartialEq + 'static>(
fn build_control_view<C: ControlData, FDT: 'static>(
fd: RwSignal<FD>,
fs: &FS,
getter: Rc<dyn FieldGetter<FD, FDT>>,
@ -130,11 +139,21 @@ impl<FD: FormToolData, FS: FormStyle> FormBuilder<FD, FS> {
render_data: crate::controls::ControlRenderData<FS, C>,
) -> (View, Box<dyn ValidationCb>) {
let (validation_signal, validation_signal_set) = create_signal(Ok(()));
let validation_fn_clone = validation_fn.clone();
let value_getter = move || {
let getter = getter.clone();
// memoize so that updating one field doesn't cause a re-render for all fields
let field = create_memo(move |_| getter(fd.get()));
unparse_fn(field.get())
let fd = fd.get();
// rerun validation if it is failing
if validation_signal.get_untracked().is_err() {
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();
@ -153,7 +172,31 @@ impl<FD: FormToolData, FS: FormStyle> FormBuilder<FD, FS> {
};
let validation_cb = Box::new(validation_cb);
let validation_cb2 = validation_cb.clone();
let value_setter = Self::create_value_setter(
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 parsed = match parse_fn(value) {
Ok(p) => {
@ -172,18 +215,9 @@ impl<FD: FormToolData, FS: FormStyle> FormBuilder<FD, FS> {
});
// run validation
(validation_cb2)();
(validation_cb)();
};
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)
Box::new(value_setter)
}
pub(crate) fn build_action_form<ServFn>(

View File

@ -79,12 +79,11 @@ impl FormStyle for GridFormStyle {
.options
.into_iter()
.map(|value| {
// let value = value;
let cloned_value = value.clone();
view! {
<option
value=value.clone()
selected=move || value_getter.get() == *cloned_value
selected=move || {value_getter.get() == *cloned_value}
>
{value.clone()}
</option>
@ -100,7 +99,7 @@ impl FormStyle for GridFormStyle {
id=&control.data.name
name=control.data.name
class="form_input"
on:change=move |ev| {
on:input=move |ev| {
value_setter(event_target_value(&ev));
}
>
@ -353,10 +352,6 @@ impl FormStyle for GridFormStyle {
.into_view()
}
fn group(&self, _control: ControlRenderData<Self, crate::controls::group::GroupData>) -> View {
todo!()
}
fn button<FD: crate::FormToolData>(
&self,
control: ControlRenderData<Self, crate::controls::button::ButtonData<FD>>,
@ -383,4 +378,13 @@ impl FormStyle for GridFormStyle {
}
.into_view()
}
fn group(&self, inner: View) -> View {
view! {
<div class="form_group form_grid">
{inner}
</div>
}
.into_view()
}
}

View File

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