use std::rc::Rc;
use vertigo::{AttrGroup, Computed, Css, Value, bind, bind_rc, component, css, dom};
use crate::{TabsParams, ValidationErrors};
mod data;
pub use data::*;
mod render;
pub use render::*;
#[derive(Clone)]
pub struct FormParams<T: 'static> {
pub css: Css,
pub add_css: Css,
pub add_section_css: Css,
pub submit_label: Rc<String>,
pub on_delete: Option<Rc<dyn Fn()>>,
pub delete_label: Rc<String>,
pub validate: Option<ValidateFunc<T>>,
pub validation_errors: Value<ValidationErrors>,
pub operation: Option<Value<Operation>>,
pub saving_label: Rc<String>,
pub saved_label: Rc<String>,
pub tabs_params: Option<TabsParams>,
}
impl<T: 'static> Default for FormParams<T> {
fn default() -> Self {
Self {
css: css! { "
display: grid;
grid-template-rows: auto 1fr;
gap: 5px;
" },
add_css: Css::default(),
add_section_css: Css::default(),
submit_label: Rc::new("Submit".to_string()),
on_delete: None,
delete_label: Rc::new("Delete".to_string()),
validate: None,
validation_errors: Default::default(),
operation: Default::default(),
saving_label: Rc::new("Saving...".to_string()),
saved_label: Rc::new("Saved".to_string()),
tabs_params: None,
}
}
}
#[component]
pub fn ModelForm<T: Clone + PartialEq>(
model: Computed<T>,
on_submit: Rc<dyn Fn(T)>,
params: FormParams<T>,
f: AttrGroup,
s: AttrGroup,
) where
FormData: From<T>,
T: From<FormExport> + 'static,
{
model.render_value(move |model| {
let form_data = Rc::new(FormData::from(model));
let on_submit = bind_rc!(on_submit, form_data, |form_export: FormExport| {
on_submit(T::from(form_export));
});
let mut form_component = Form {
form_data,
on_submit,
params: params.clone(),
}
.into_component();
form_component.f = f.clone();
form_component.s = s.clone();
form_component.mount()
})
}
#[component]
pub fn Form<T>(
form_data: Rc<FormData>,
on_submit: Rc<dyn Fn(FormExport)>,
params: FormParams<T>,
f: AttrGroup,
s: AttrGroup,
) where
T: From<FormExport> + 'static,
{
let subgrid_css = css! {"
display: grid;
grid-template-columns: subgrid;
grid-column: span 2 / span 2;
"};
let validation_errors = params.validation_errors.clone();
let controls = |params: &FormParams<T>, c_config: &ControlsConfig| {
let mut controls = vec![];
let ctrl_item_css = css! {"
margin: 5px;
"};
if c_config.submit {
controls.push(dom! {
<input css={&ctrl_item_css} type="submit" value={¶ms.submit_label} />
});
}
if c_config.delete
&& let Some(on_click) = params.on_delete.clone()
{
controls.push(dom! {
<input css={&ctrl_item_css} type="submit" value={¶ms.delete_label} on_click={move |_| on_click()} />
});
}
let errors = validation_errors
.render_value_option(|errs| errs.get("submit").map(|err| dom! { <span>{err}</span> }));
let operation_str = params.operation.as_ref().map(|operation| {
bind!(
params.saving_label,
params.saved_label,
operation.render_value_option(move |oper| {
let mut css = ctrl_item_css.clone();
match oper {
Operation::Saving => Some(saving_label.clone()),
Operation::Success => Some(saved_label.clone()),
Operation::Error(err) => {
css += css! {"color: red;"};
Some(err)
}
_ => None,
}
.map(|operation_str| dom! { <span {css}>{operation_str}</span> })
})
)
});
if controls.is_empty() {
None
} else {
let mut css_controls = css!("grid-column: span 2;");
if let Some(custom_css) = &c_config.css {
css_controls += custom_css;
}
Some(dom! {
<div css={css_controls}>
{..controls}
{errors}
{..operation_str}
</div>
})
}
};
let top_controls = controls(¶ms, &form_data.top_controls);
let bottom_controls = controls(¶ms, &form_data.bottom_controls);
let section_css = subgrid_css + params.add_section_css;
let fields = fields(
&form_data.sections,
&s,
validation_errors.clone(),
§ion_css,
);
let tabs = tabs(
&form_data.tabs,
¶ms.tabs_params,
&s,
validation_errors.clone(),
§ion_css,
¶ms.css.clone(),
);
let form_css = params.css + params.add_css;
let on_submit = bind_rc!(form_data, validation_errors, || {
params
.operation
.as_ref()
.inspect(|operation| operation.set(Operation::Saving));
let model = form_data.export();
let valid = if let Some(validate) = ¶ms.validate {
validate(&model.clone().into(), validation_errors.clone())
} else {
true
};
if valid {
on_submit(model);
}
});
dom! {
<form css={form_css} on_submit={on_submit} {..f}>
{..top_controls}
{..fields}
{..tabs}
{..bottom_controls}
</form>
}
}