use crate::state::{StateHandle, StateSetter};
use std::{collections::HashMap, sync::Arc};
use super::validation::Validator;
#[derive(Clone)]
pub struct FormConfig {
pub(crate) initial_values: HashMap<String, String>,
pub(crate) validators: HashMap<String, Vec<Validator>>,
pub(crate) on_submit: Arc<dyn Fn(HashMap<String, String>) + Send + Sync>,
}
impl FormConfig {
pub fn builder() -> FormConfigBuilder {
FormConfigBuilder::new()
}
}
pub struct FormConfigBuilder {
initial_values: HashMap<String, String>,
validators: HashMap<String, Vec<Validator>>,
#[allow(clippy::type_complexity)]
on_submit: Option<Arc<dyn Fn(HashMap<String, String>) + Send + Sync>>,
}
impl FormConfigBuilder {
pub fn new() -> Self {
Self {
initial_values: HashMap::new(),
validators: HashMap::new(),
on_submit: None,
}
}
pub fn field(mut self, name: impl Into<String>, initial_value: impl Into<String>) -> Self {
self.initial_values
.insert(name.into(), initial_value.into());
self
}
pub fn fields(mut self, fields: HashMap<String, String>) -> Self {
self.initial_values.extend(fields);
self
}
pub fn validate(mut self, field: impl Into<String>, validators: Vec<Validator>) -> Self {
self.validators.insert(field.into(), validators);
self
}
pub fn validator(mut self, field: impl Into<String>, validator: Validator) -> Self {
let field = field.into();
self.validators.entry(field).or_default().push(validator);
self
}
pub fn on_submit<F>(mut self, handler: F) -> Self
where
F: Fn(HashMap<String, String>) + Send + Sync + 'static,
{
self.on_submit = Some(Arc::new(handler));
self
}
pub fn build(self) -> FormConfig {
FormConfig {
initial_values: self.initial_values,
validators: self.validators,
on_submit: self
.on_submit
.expect("on_submit handler must be set. Use build_with_default_submit() for a no-op handler."),
}
}
pub fn build_with_default_submit(self) -> FormConfig {
FormConfig {
initial_values: self.initial_values,
validators: self.validators,
on_submit: self.on_submit.unwrap_or_else(|| Arc::new(|_| {})),
}
}
}
impl Default for FormConfigBuilder {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone)]
pub struct FormHandle {
pub(crate) values: StateHandle<HashMap<String, String>>,
pub(crate) set_values: StateSetter<HashMap<String, String>>,
pub(crate) errors: StateHandle<HashMap<String, String>>,
pub(crate) set_errors: StateSetter<HashMap<String, String>>,
pub(crate) touched: StateHandle<HashMap<String, bool>>,
pub(crate) set_touched: StateSetter<HashMap<String, bool>>,
pub(crate) is_submitting: StateHandle<bool>,
pub(crate) set_is_submitting: StateSetter<bool>,
pub(crate) is_valid: StateHandle<bool>,
pub(crate) set_is_valid: StateSetter<bool>,
pub(crate) validators: HashMap<String, Vec<Validator>>,
pub(crate) on_submit: Arc<dyn Fn(HashMap<String, String>) + Send + Sync>,
}
impl Default for FormHandle {
fn default() -> Self {
Self {
values: StateHandle::default(),
set_values: StateSetter::default(),
errors: StateHandle::default(),
set_errors: StateSetter::default(),
touched: StateHandle::default(),
set_touched: StateSetter::default(),
is_submitting: StateHandle::default(),
set_is_submitting: StateSetter::default(),
is_valid: StateHandle::default(),
set_is_valid: StateSetter::default(),
validators: HashMap::new(),
on_submit: Arc::new(|_| {}),
}
}
}
impl FormHandle {
pub fn register(&self, name: &str) -> FieldRegistration {
FieldRegistration {
name: name.to_string(),
value: self.get_value(name).unwrap_or_default(),
error: self.get_error(name),
touched: self.is_touched(name),
}
}
pub fn get_value(&self, name: &str) -> Option<String> {
self.values.get().get(name).cloned()
}
pub fn set_value(&self, name: &str, value: String) {
let mut values = self.values.get();
values.insert(name.to_string(), value.clone());
self.set_values.set(values);
if self.is_touched(name) {
self.validate_field(name, &value);
}
}
pub fn get_error(&self, name: &str) -> Option<String> {
self.errors.get().get(name).cloned()
}
pub fn set_error(&self, name: &str, error: Option<String>) {
let mut errors = self.errors.get();
if let Some(err) = error {
errors.insert(name.to_string(), err);
} else {
errors.remove(name);
}
self.set_errors.set(errors);
}
pub fn is_touched(&self, name: &str) -> bool {
self.touched.get().get(name).copied().unwrap_or(false)
}
pub fn set_touched(&self, name: &str, is_touched: bool) {
let mut touched = self.touched.get();
touched.insert(name.to_string(), is_touched);
self.set_touched.set(touched);
}
pub fn validate_field(&self, name: &str, value: &str) -> bool {
if let Some(validators) = self.validators.get(name) {
for validator in validators {
if let Some(error) = validator.validate(value) {
self.set_error(name, Some(error));
return false;
}
}
}
self.set_error(name, None);
true
}
pub fn validate_all(&self) -> bool {
let values = self.values.get();
let mut all_valid = true;
for (name, value) in values.iter() {
if !self.validate_field(name, value) {
all_valid = false;
}
}
self.set_is_valid.set(all_valid);
all_valid
}
pub fn reset(&self, initial_values: HashMap<String, String>) {
self.set_values.set(initial_values);
self.set_errors.set(HashMap::new());
self.set_touched.set(HashMap::new());
self.set_is_submitting.set(false);
self.set_is_valid.set(true);
}
pub fn submit(&self) {
let values = self.values.get();
let mut touched = HashMap::new();
for name in values.keys() {
touched.insert(name.clone(), true);
}
self.set_touched.set(touched);
if self.validate_all() {
self.set_is_submitting.set(true);
(self.on_submit)(self.values.get());
self.set_is_submitting.set(false);
}
}
pub fn is_submitting(&self) -> bool {
self.is_submitting.get()
}
pub fn is_valid(&self) -> bool {
self.is_valid.get()
}
pub fn get_values(&self) -> HashMap<String, String> {
self.values.get()
}
pub fn get_all_values(&self) -> HashMap<String, String> {
self.get_values()
}
pub fn get_errors(&self) -> HashMap<String, String> {
self.errors.get()
}
pub fn has_errors(&self) -> bool {
!self.errors.get().is_empty()
}
pub fn is_dirty(&self) -> bool {
!self.touched.get().is_empty()
}
}
#[derive(Debug, Clone)]
pub struct FieldRegistration {
pub name: String,
pub value: String,
pub error: Option<String>,
pub touched: bool,
}
impl FieldRegistration {
pub fn has_error(&self) -> bool {
self.error.is_some()
}
pub fn error_message(&self) -> Option<&str> {
self.error.as_deref()
}
}