presolve_compiler/
form_reset.rs1use std::collections::BTreeMap;
4
5use crate::{
6 FieldBindingId, FieldId, FieldResetOperationId, FormEntity, FormFieldBinding, FormFieldEntity,
7 FormId, FormTrackingProducts, ResetPlanId, SerializableValue, SourceProvenance,
8};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum FieldResetStep {
12 RestoreInitialValue,
13 WriteBoundControls,
14 ClearDirty,
15 ClearTouched,
16 ClearValidation,
17}
18
19#[derive(Debug, Clone, PartialEq, Eq)]
20pub struct FieldResetOperation {
21 pub id: FieldResetOperationId,
22 pub field: FieldId,
23 pub initial_value: SerializableValue,
24 pub declaration_order: usize,
25 pub bound_controls: Vec<FieldBindingId>,
26 pub steps: Vec<FieldResetStep>,
27 pub provenance: SourceProvenance,
28}
29
30#[derive(Debug, Clone, PartialEq, Eq)]
31pub struct FormResetPlan {
32 pub id: ResetPlanId,
33 pub form: FormId,
34 pub operations: Vec<FieldResetOperation>,
35 pub clear_aggregate_validity: bool,
36 pub clear_submission_state: bool,
37 pub schedule_validation: bool,
38}
39
40#[derive(Debug, Clone, PartialEq, Eq, Default)]
41pub struct ResetProducts {
42 pub plans: BTreeMap<ResetPlanId, FormResetPlan>,
43}
44
45#[must_use]
46pub fn collect_reset_products(
51 forms: &BTreeMap<FormId, FormEntity>,
52 fields: &BTreeMap<FieldId, FormFieldEntity>,
53 bindings: &BTreeMap<FieldBindingId, FormFieldBinding>,
54 tracking: &FormTrackingProducts,
55) -> ResetProducts {
56 let mut plans = BTreeMap::new();
57 for form in forms.values() {
58 let id = ResetPlanId::for_form(&form.id);
59 let mut form_fields = fields
60 .values()
61 .filter(|field| field.owner_form == form.id)
62 .collect::<Vec<_>>();
63 form_fields.sort_by(|left, right| {
64 (left.declaration_order, &left.id).cmp(&(right.declaration_order, &right.id))
65 });
66 let operations = form_fields
67 .into_iter()
68 .map(|field| {
69 let mut bound_controls = bindings
70 .values()
71 .filter(|binding| binding.field == field.id)
72 .collect::<Vec<_>>();
73 bound_controls.sort_by(|left, right| {
74 (left.authored_order, &left.id).cmp(&(right.authored_order, &right.id))
75 });
76 assert!(
77 tracking.dirty.tracking(&field.id).is_some()
78 && tracking.touched.tracking(&field.id).is_some(),
79 "I11 requires exact I8 tracking records"
80 );
81 FieldResetOperation {
82 id: FieldResetOperationId::for_plan_and_field(&id, &field.id),
83 field: field.id.clone(),
84 initial_value: field.initial_value.clone(),
85 declaration_order: field.declaration_order,
86 bound_controls: bound_controls
87 .into_iter()
88 .map(|binding| binding.id.clone())
89 .collect(),
90 steps: vec![
91 FieldResetStep::RestoreInitialValue,
92 FieldResetStep::WriteBoundControls,
93 FieldResetStep::ClearDirty,
94 FieldResetStep::ClearTouched,
95 FieldResetStep::ClearValidation,
96 ],
97 provenance: field.provenance.clone(),
98 }
99 })
100 .collect();
101 plans.insert(
102 id.clone(),
103 FormResetPlan {
104 id,
105 form: form.id.clone(),
106 operations,
107 clear_aggregate_validity: true,
108 clear_submission_state: true,
109 schedule_validation: false,
110 },
111 );
112 }
113 ResetProducts { plans }
114}
115
116#[cfg(test)]
117mod tests {
118 #[test]
119 fn creates_ordered_non_revalidating_reset_operations() {
120 let parsed = presolve_parser::parse_file(
121 "src/Form.tsx",
122 r#"@component("x") class X { @form() form!: Form; @field(this.form) first = "a"; @field(this.form) second = false; render() { return <input field={this.first} />; } }"#,
123 );
124 let model = crate::build_application_semantic_model(&parsed);
125 let form = crate::FormId::for_owner(&model.components[0].id, "form");
126 let plan = &model.reset.plans[&crate::ResetPlanId::for_form(&form)];
127 assert!(!plan.schedule_validation);
128 assert_eq!(plan.operations.len(), 2);
129 assert_eq!(plan.operations[0].bound_controls.len(), 1);
130 assert!(plan.operations[1].bound_controls.is_empty());
131 }
132}