1use crate::state::{StateHandle, StateSetter};
4use std::{collections::HashMap, sync::Arc};
5
6use super::validation::Validator;
7
8#[derive(Clone)]
10pub struct FormConfig {
11 pub(crate) initial_values: HashMap<String, String>,
13
14 pub(crate) validators: HashMap<String, Vec<Validator>>,
16 pub(crate) on_submit: Arc<dyn Fn(HashMap<String, String>) + Send + Sync>,
18}
19
20impl FormConfig {
21 pub fn builder() -> FormConfigBuilder {
41 FormConfigBuilder::new()
42 }
43}
44
45pub struct FormConfigBuilder {
50 initial_values: HashMap<String, String>,
51 validators: HashMap<String, Vec<Validator>>,
52 #[allow(clippy::type_complexity)]
53 on_submit: Option<Arc<dyn Fn(HashMap<String, String>) + Send + Sync>>,
54}
55
56impl FormConfigBuilder {
57 pub fn new() -> Self {
59 Self {
60 initial_values: HashMap::new(),
61 validators: HashMap::new(),
62 on_submit: None,
63 }
64 }
65
66 pub fn field(mut self, name: impl Into<String>, initial_value: impl Into<String>) -> Self {
77 self.initial_values
78 .insert(name.into(), initial_value.into());
79 self
80 }
81
82 pub fn fields(mut self, fields: HashMap<String, String>) -> Self {
96 self.initial_values.extend(fields);
97 self
98 }
99
100 pub fn validate(mut self, field: impl Into<String>, validators: Vec<Validator>) -> Self {
114 self.validators.insert(field.into(), validators);
115 self
116 }
117
118 pub fn validator(mut self, field: impl Into<String>, validator: Validator) -> Self {
130 let field = field.into();
131 self.validators.entry(field).or_default().push(validator);
132 self
133 }
134
135 pub fn on_submit<F>(mut self, handler: F) -> Self
147 where
148 F: Fn(HashMap<String, String>) + Send + Sync + 'static,
149 {
150 self.on_submit = Some(Arc::new(handler));
151 self
152 }
153
154 pub fn build(self) -> FormConfig {
161 FormConfig {
162 initial_values: self.initial_values,
163 validators: self.validators,
164 on_submit: self
165 .on_submit
166 .expect("on_submit handler must be set. Use build_with_default_submit() for a no-op handler."),
167 }
168 }
169
170 pub fn build_with_default_submit(self) -> FormConfig {
174 FormConfig {
175 initial_values: self.initial_values,
176 validators: self.validators,
177 on_submit: self.on_submit.unwrap_or_else(|| Arc::new(|_| {})),
178 }
179 }
180}
181
182impl Default for FormConfigBuilder {
183 fn default() -> Self {
184 Self::new()
185 }
186}
187
188#[derive(Clone)]
190pub struct FormHandle {
191 pub(crate) values: StateHandle<HashMap<String, String>>,
192 pub(crate) set_values: StateSetter<HashMap<String, String>>,
193 pub(crate) errors: StateHandle<HashMap<String, String>>,
194 pub(crate) set_errors: StateSetter<HashMap<String, String>>,
195 pub(crate) touched: StateHandle<HashMap<String, bool>>,
196 pub(crate) set_touched: StateSetter<HashMap<String, bool>>,
197 pub(crate) is_submitting: StateHandle<bool>,
198 pub(crate) set_is_submitting: StateSetter<bool>,
199 pub(crate) is_valid: StateHandle<bool>,
200 pub(crate) set_is_valid: StateSetter<bool>,
201 pub(crate) validators: HashMap<String, Vec<Validator>>,
202 pub(crate) on_submit: Arc<dyn Fn(HashMap<String, String>) + Send + Sync>,
203}
204
205impl Default for FormHandle {
206 fn default() -> Self {
207 Self {
208 values: StateHandle::default(),
209 set_values: StateSetter::default(),
210 errors: StateHandle::default(),
211 set_errors: StateSetter::default(),
212 touched: StateHandle::default(),
213 set_touched: StateSetter::default(),
214 is_submitting: StateHandle::default(),
215 set_is_submitting: StateSetter::default(),
216 is_valid: StateHandle::default(),
217 set_is_valid: StateSetter::default(),
218 validators: HashMap::new(),
219 on_submit: Arc::new(|_| {}),
220 }
221 }
222}
223
224impl FormHandle {
225 pub fn register(&self, name: &str) -> FieldRegistration {
227 FieldRegistration {
228 name: name.to_string(),
229 value: self.get_value(name).unwrap_or_default(),
230 error: self.get_error(name),
231 touched: self.is_touched(name),
232 }
233 }
234
235 pub fn get_value(&self, name: &str) -> Option<String> {
237 self.values.get().get(name).cloned()
238 }
239
240 pub fn set_value(&self, name: &str, value: String) {
242 let mut values = self.values.get();
243 values.insert(name.to_string(), value.clone());
244 self.set_values.set(values);
245
246 if self.is_touched(name) {
248 self.validate_field(name, &value);
249 }
250 }
251
252 pub fn get_error(&self, name: &str) -> Option<String> {
254 self.errors.get().get(name).cloned()
255 }
256
257 pub fn set_error(&self, name: &str, error: Option<String>) {
259 let mut errors = self.errors.get();
260 if let Some(err) = error {
261 errors.insert(name.to_string(), err);
262 } else {
263 errors.remove(name);
264 }
265 self.set_errors.set(errors);
266 }
267
268 pub fn is_touched(&self, name: &str) -> bool {
270 self.touched.get().get(name).copied().unwrap_or(false)
271 }
272
273 pub fn set_touched(&self, name: &str, is_touched: bool) {
275 let mut touched = self.touched.get();
276 touched.insert(name.to_string(), is_touched);
277 self.set_touched.set(touched);
278 }
279
280 pub fn validate_field(&self, name: &str, value: &str) -> bool {
282 if let Some(validators) = self.validators.get(name) {
283 for validator in validators {
284 if let Some(error) = validator.validate(value) {
285 self.set_error(name, Some(error));
286 return false;
287 }
288 }
289 }
290 self.set_error(name, None);
291 true
292 }
293
294 pub fn validate_all(&self) -> bool {
296 let values = self.values.get();
297 let mut all_valid = true;
298
299 for (name, value) in values.iter() {
300 if !self.validate_field(name, value) {
301 all_valid = false;
302 }
303 }
304
305 self.set_is_valid.set(all_valid);
306 all_valid
307 }
308
309 pub fn reset(&self, initial_values: HashMap<String, String>) {
311 self.set_values.set(initial_values);
312 self.set_errors.set(HashMap::new());
313 self.set_touched.set(HashMap::new());
314 self.set_is_submitting.set(false);
315 self.set_is_valid.set(true);
316 }
317
318 pub fn submit(&self) {
320 let values = self.values.get();
322 let mut touched = HashMap::new();
323 for name in values.keys() {
324 touched.insert(name.clone(), true);
325 }
326 self.set_touched.set(touched);
327
328 if self.validate_all() {
330 self.set_is_submitting.set(true);
331 (self.on_submit)(self.values.get());
332 self.set_is_submitting.set(false);
333 }
334 }
335
336 pub fn is_submitting(&self) -> bool {
338 self.is_submitting.get()
339 }
340
341 pub fn is_valid(&self) -> bool {
343 self.is_valid.get()
344 }
345
346 pub fn get_values(&self) -> HashMap<String, String> {
348 self.values.get()
349 }
350
351 pub fn get_all_values(&self) -> HashMap<String, String> {
353 self.get_values()
354 }
355
356 pub fn get_errors(&self) -> HashMap<String, String> {
358 self.errors.get()
359 }
360
361 pub fn has_errors(&self) -> bool {
363 !self.errors.get().is_empty()
364 }
365
366 pub fn is_dirty(&self) -> bool {
368 !self.touched.get().is_empty()
369 }
370}
371
372#[derive(Debug, Clone)]
374pub struct FieldRegistration {
375 pub name: String,
377
378 pub value: String,
380
381 pub error: Option<String>,
383
384 pub touched: bool,
386}
387
388impl FieldRegistration {
389 pub fn has_error(&self) -> bool {
391 self.error.is_some()
392 }
393
394 pub fn error_message(&self) -> Option<&str> {
396 self.error.as_deref()
397 }
398}