use std::{collections::HashMap, rc::Rc};
use vertigo::{Computed, Css, DomElement, Value, transaction};
use super::{
DataFieldValue, FormExport,
data_field::{BoolValue, DictValue, ImageValue, ListValue, MultiValue, StringValue},
};
#[derive(Default)]
pub struct FormData {
pub sections: Vec<DataSection>,
pub tabs: Vec<(String, Rc<Vec<DataSection>>)>,
pub top_controls: ControlsConfig,
pub bottom_controls: ControlsConfig,
}
#[derive(Default)]
pub struct ControlsConfig {
pub css: Option<Css>,
pub submit: bool,
pub delete: bool,
}
impl ControlsConfig {
pub fn full() -> Self {
Self {
css: None,
submit: true,
delete: true,
}
}
pub fn with_css(mut self, css: Css) -> Self {
self.css = Some(css);
self
}
}
impl FormData {
pub fn with(mut self, section: DataSection) -> Self {
self.sections.push(section);
self
}
pub fn add_tab(mut self, tab_label: impl Into<String>, sections: Vec<DataSection>) -> Self {
self.tabs.push((tab_label.into(), Rc::new(sections)));
self
}
pub fn add_top_controls(mut self) -> Self {
self.top_controls = ControlsConfig::full();
self
}
pub fn add_top_controls_styled(mut self, css: Css) -> Self {
self.top_controls = ControlsConfig::full().with_css(css);
self
}
pub fn add_bottom_controls(mut self) -> Self {
self.bottom_controls = ControlsConfig::full();
self
}
pub fn add_bottom_controls_styled(mut self, css: Css) -> Self {
self.bottom_controls = ControlsConfig::full().with_css(css);
self
}
pub fn export(&self) -> FormExport {
let mut hash_map = HashMap::new();
transaction(|ctx| {
for (_, sections) in &self.tabs {
for section in sections.iter() {
for field in §ion.fields {
hash_map.insert(field.key.clone(), field.value.export(ctx));
}
}
}
for section in &self.sections {
for field in §ion.fields {
hash_map.insert(field.key.clone(), field.value.export(ctx));
}
}
});
FormExport::new(hash_map)
}
}
#[derive(Clone, Copy, Default, PartialEq)]
pub enum FieldsetStyle {
#[default]
Plain,
Dimensions,
}
#[derive(Default)]
pub struct DataSection {
pub label: String,
pub fields: Vec<DataField>,
pub error: Option<String>,
pub render: Option<Rc<dyn Fn(Vec<DataField>) -> DomElement>>,
pub fieldset_style: FieldsetStyle,
pub fieldset_css: Option<Css>,
pub new_group: bool,
}
#[derive(Clone)]
pub struct DataField {
pub key: String,
pub value: DataFieldValue,
}
impl DataSection {
pub fn new(label: impl Into<String>) -> Self {
Self {
label: label.into(),
..Default::default()
}
}
pub fn with_string_field(
label: impl Into<String>,
key: impl Into<String>,
original_value: impl Into<String>,
) -> Self {
let value = original_value.into();
Self {
label: label.into(),
fields: vec![DataField {
key: key.into(),
value: DataFieldValue::String(StringValue {
value: Value::new(value.clone()),
original_value: Rc::new(value),
}),
}],
..Default::default()
}
}
pub fn with_opt_string_field(
label: impl Into<String>,
key: impl Into<String>,
original_value: &Option<String>,
) -> Self {
Self::with_string_field(label, key, original_value.clone().unwrap_or_default())
}
pub fn add_field(mut self, key: impl Into<String>, value: DataFieldValue) -> Self {
self.fields.push(DataField {
key: key.into(),
value,
});
self
}
pub fn add_string_field(
mut self,
key: impl Into<String>,
original_value: impl Into<String>,
) -> Self {
let value = original_value.into();
self.fields.push(DataField {
key: key.into(),
value: DataFieldValue::String(StringValue {
value: Value::new(value.clone()),
original_value: Rc::new(value),
}),
});
self
}
pub fn add_opt_string_field(
self,
key: impl Into<String>,
original_value: &Option<String>,
) -> Self {
self.add_string_field(key, original_value.clone().unwrap_or_default())
}
pub fn add_list_field(
mut self,
key: impl Into<String>,
original_value: Option<impl Into<String>>,
options: Vec<String>,
) -> Self {
let value = original_value.map(|s| s.into());
let options = Computed::from(move |_ctx| options.clone());
self.fields.push(DataField {
key: key.into(),
value: DataFieldValue::List(ListValue {
value: Value::new(value.clone().unwrap_or_default()),
original_value: value.map(Rc::new),
options,
}),
});
self
}
pub fn add_static_dict_field(
self,
key: impl Into<String>,
original_value: Option<i64>,
options: Vec<(i64, String)>,
) -> Self {
let options = Computed::from(move |_ctx| options.clone());
self.add_dict_field(key, original_value, options)
}
pub fn add_dict_field(
mut self,
key: impl Into<String>,
original_value: Option<i64>,
options: Computed<Vec<(i64, String)>>,
) -> Self {
self.fields.push(DataField {
key: key.into(),
value: DataFieldValue::Dict(DictValue {
value: Value::new(original_value.unwrap_or_default()),
original_value: original_value.map(Rc::new),
options,
}),
});
self
}
pub fn add_multiselect_field(
mut self,
key: impl Into<String>,
original_value: Vec<i64>,
options: Computed<HashMap<i64, String>>,
add_label: impl Into<String>,
) -> Self {
self.fields.push(DataField {
key: key.into(),
value: DataFieldValue::Multi(MultiValue {
value: Value::new(original_value.iter().cloned().map(Value::new).collect()),
original_value: Rc::new(original_value),
options,
add_label: Rc::new(add_label.into()),
}),
});
self
}
pub fn add_bool_field(
mut self,
key: impl Into<String>,
original_value: Option<impl Into<bool>>,
) -> Self {
let value = original_value.map(|b| b.into());
self.fields.push(DataField {
key: key.into(),
value: DataFieldValue::Bool(BoolValue {
value: Value::new(value.unwrap_or_default()),
original_value: value.map(Rc::new),
}),
});
self
}
pub fn add_image_field(
mut self,
key: impl Into<String>,
original_value: Option<impl Into<String>>,
) -> Self {
let value = original_value.map(|l| l.into());
self.fields.push(DataField {
key: key.into(),
value: DataFieldValue::Image(ImageValue {
value: Value::new(None),
original_link: value.map(Rc::new),
component_params: None,
}),
});
self
}
pub fn set_fieldset_style(mut self, fieldset_style: FieldsetStyle) -> Self {
self.fieldset_style = fieldset_style;
self
}
pub fn set_fieldset_css(mut self, fieldset_css: Css) -> Self {
self.fieldset_css = Some(fieldset_css);
self
}
pub fn starts_new_group(mut self) -> Self {
self.new_group = true;
self
}
}