generated from mitchell/rust_template
impl groups
This commit is contained in:
parent
fb47d553e7
commit
31b24a23a1
@ -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 {
|
||||
|
||||
@ -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
|
||||
|
||||
for validation in group_builder.validations {
|
||||
self.validations.push(validation);
|
||||
}
|
||||
|
||||
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());
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>(
|
||||
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user