Skip to main content

ferroui_controls/
data_validation_errors.rs

1use crate::metadata::PseudoClassesAttribute;
2use crate::primitives::TemplatedControlImpl;
3use crate::templates::IDataTemplate;
4use crate::{ContentControl, ContentControlImpl, Control, ControlImpl};
5use ferroui_base::data::{
6    AggregateError, AggregateException, BindingChainException, BindingError, DataValidationException,
7};
8use ferroui_base::input::InputElementImpl;
9use ferroui_base::interactivity::InteractiveImpl;
10use ferroui_base::layout::LayoutableImpl;
11use ferroui_base::{
12    ferro_class, ferro_impl_classes, ferro_property, instantiate, AttachedProperty, BoxedValue, DirectProperty,
13    ElementRef, FerroObject, FerroObjectImpl, FerroProperty, FerroPropertyChangedEventArgs, Ref,
14    StyledElement, StyledElementImpl, StyledProperty, VisualImpl,
15};
16use std::cell::RefCell;
17use std::fmt;
18use std::rc::Rc;
19
20/// Converts an error of a control into the object that is shown for it; an
21/// error converted to `None` is dropped.
22///
23/// Two converters are equal when they are the same function object.
24#[derive(Clone)]
25pub struct ErrorConverter(Rc<dyn Fn(&BoxedValue) -> Option<BoxedValue>>);
26
27impl ErrorConverter {
28    pub fn new(convert: impl Fn(&BoxedValue) -> Option<BoxedValue> + 'static) -> Self {
29        Self(Rc::new(convert))
30    }
31
32    /// Converts an error.
33    pub fn convert(&self, error: &BoxedValue) -> Option<BoxedValue> {
34        (self.0)(error)
35    }
36}
37
38impl PartialEq for ErrorConverter {
39    fn eq(&self, other: &Self) -> bool {
40        Rc::ptr_eq(&self.0, &other.0)
41    }
42}
43
44impl fmt::Debug for ErrorConverter {
45    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46        f.write_str("ErrorConverter")
47    }
48}
49
50/// A control which displays an error notifier when there is a data
51/// validation error. Provides attached properties to track errors on a
52/// control.
53///
54/// You will probably only want to create instances inside of control
55/// templates. Pseudo-classes (on the control with the errors): `:error`.
56///
57/// An error is an untyped value: the error data of a data validation
58/// error, or the error itself boxed as a [`BindingError`].
59#[repr(C)]
60pub struct DataValidationErrors {
61    base: ContentControl,
62    owner: RefCell<Option<ElementRef<Control>>>,
63}
64
65ferro_class!(DataValidationErrors: ContentControl);
66ferroui_base::ferro_class_info!(DataValidationErrors { new: DataValidationErrors::new });
67ferro_impl_classes!(
68    DataValidationErrors: StyledElementImpl,
69    VisualImpl,
70    LayoutableImpl,
71    InteractiveImpl,
72    InputElementImpl,
73    ControlImpl,
74    TemplatedControlImpl,
75    ContentControlImpl
76);
77
78ferroui_base::ferro_impl_classes!(DataValidationErrors: FerroObjectImpl);
79
80impl DataValidationErrors {
81    /// The pseudoclasses set by the class.
82    pub const PSEUDO_CLASSES: PseudoClassesAttribute = PseudoClassesAttribute::new(&[":error"]);
83}
84
85ferroui_base::ferro_properties! { impl DataValidationErrors {
86    ferro_property!(
87        /// Defines the `DataValidationErrors.Errors` attached property.
88        pub fn errors_property() -> AttachedProperty<Option<Vec<BoxedValue>>> {
89            FerroProperty::register_attached::<DataValidationErrors, Control, _>("Errors", None)
90        }
91    );
92
93    ferro_property!(
94        /// Defines the `DataValidationErrors.HasErrors` attached property.
95        pub fn has_errors_property() -> AttachedProperty<bool> {
96            FerroProperty::register_attached::<DataValidationErrors, Control, _>("HasErrors", false)
97        }
98    );
99
100    ferro_property!(
101        /// Defines the `DataValidationErrors.ErrorConverter` attached
102        /// property.
103        pub fn error_converter_property() -> AttachedProperty<Option<ErrorConverter>> {
104            FerroProperty::register_attached::<DataValidationErrors, Control, _>("ErrorConverter", None)
105        }
106    );
107
108    ferro_property!(
109        /// Defines the `DataValidationErrors.ErrorTemplate` property.
110        pub fn error_template_property() -> StyledProperty<Option<Rc<dyn IDataTemplate>>> {
111            FerroProperty::register::<DataValidationErrors, _>("ErrorTemplate", None)
112        }
113    );
114
115    ferro_property!(
116        /// Stores the original, not converted errors passed by the control.
117        fn original_errors_property() -> AttachedProperty<Option<Vec<BoxedValue>>> {
118            FerroProperty::register_attached::<DataValidationErrors, Control, _>("OriginalErrors", None)
119        }
120    );
121
122    ferro_property!(
123        /// Prevents executing `errors_changed` after the errors are updated
124        /// internally from `on_errors_or_converter_changed`.
125        fn overriding_errors_internally_property() -> AttachedProperty<bool> {
126            FerroProperty::register_attached::<DataValidationErrors, Control, _>("OverridingErrorsInternally", false)
127        }
128    );
129
130    ferro_property!(
131        /// Defines the `Owner` property.
132        ///
133        /// Deviation (DEVIATIONS.md, Data validation): the owner is the
134        /// control the errors are shown in, an ancestor of this control,
135        /// which this control does not own, so the value is an element
136        /// reference where the managed property holds the control. The themes make it the `DataContext` of a
137        /// part of the template (`DataContext="{TemplateBinding Owner}"`),
138        /// which holds the reference as it is.
139        pub fn owner_property() -> DirectProperty<DataValidationErrors, Option<ElementRef<Control>>> {
140            ferroui_base::data::core::ValueTypes::register_element_ref::<Control>();
141            FerroProperty::register_direct::<DataValidationErrors, _>(
142                "Owner",
143                |o| o.owner.borrow().clone(),
144                Some(|o, v| o.set_owner(ElementRef::resolve(&v))),
145                None,
146            )
147        }
148    );
149} }
150
151impl DataValidationErrors {
152    /// Initializes the static members of the `DataValidationErrors` class.
153    fn static_constructor() {
154        Self::errors_property().changed().subscribe(Self::errors_changed);
155        Self::has_errors_property().changed().subscribe(Self::has_errors_changed);
156        StyledElement::templated_parent_property()
157            .changed()
158            .add_class_handler::<DataValidationErrors>(|x, e| x.on_templated_parent_change(e));
159        Self::error_converter_property().changed().subscribe(Self::on_error_converter_changed);
160    }
161
162    /// Creates the class data; see [`ferroui_base::FerroObject::construct`].
163    pub fn construct() -> Self {
164        Self { base: ContentControl::construct(), owner: RefCell::new(None) }
165    }
166
167    /// Initializes a new instance of the `DataValidationErrors` class.
168    pub fn new() -> Ref<Self> {
169        instantiate(Self::construct())
170    }
171
172    /// The control whose errors are displayed.
173    pub fn owner(&self) -> Option<Ref<Control>> {
174        ElementRef::resolve(&self.owner.borrow())
175    }
176
177    pub fn set_owner(&self, value: Option<Ref<Control>>) {
178        self.set_and_raise(Self::owner_property(), &self.owner, ElementRef::from_nullable(value));
179    }
180
181    /// The control of a change of one of the attached properties.
182    ///
183    /// # Panics
184    ///
185    /// When the sender is not a control: the attached properties are
186    /// defined for controls.
187    fn sender_control(e: &FerroPropertyChangedEventArgs<'_>) -> Ref<Control> {
188        match e.sender().downcast_ref::<Control>() {
189            Some(control) => control.to_ref(),
190            None => panic!(
191                "Unable to cast object of type '{}' to type 'Control'.",
192                e.sender().get_type().name()
193            ),
194        }
195    }
196
197    fn on_error_converter_changed(e: &FerroPropertyChangedEventArgs<'_>) {
198        Self::on_errors_or_converter_changed(&Self::sender_control(e));
199    }
200
201    fn on_templated_parent_change(&self, e: &FerroPropertyChangedEventArgs<'_>) {
202        if self.owner().is_none() {
203            let new_value = e.get_new_value::<Option<Ref<FerroObject>>>();
204            self.set_owner(new_value.and_then(|value| value.cast::<Control>()));
205        }
206    }
207
208    /// The data template of the errors.
209    pub fn error_template(&self) -> Option<Rc<dyn IDataTemplate>> {
210        self.get_value(Self::error_template_property())
211    }
212
213    pub fn set_error_template(&self, value: Option<Rc<dyn IDataTemplate>>) {
214        self.set_value(Self::error_template_property(), value)
215    }
216
217    fn errors_changed(e: &FerroPropertyChangedEventArgs<'_>) {
218        let control = Self::sender_control(e);
219
220        if control.get_value(Self::overriding_errors_internally_property()) {
221            return;
222        }
223
224        let errors = e.get_new_value::<Option<Vec<BoxedValue>>>();
225
226        // Update the original errors.
227        control.set_value(Self::original_errors_property(), errors);
228
229        Self::on_errors_or_converter_changed(&control);
230    }
231
232    fn has_errors_changed(e: &FerroPropertyChangedEventArgs<'_>) {
233        let control = Self::sender_control(e);
234        control.pseudo_classes().set(":error", e.get_new_value::<bool>());
235    }
236
237    /// Gets the errors of a control.
238    pub fn get_errors(control: &Control) -> Option<Vec<BoxedValue>> {
239        control.get_value(Self::errors_property())
240    }
241
242    /// Sets the errors of a control.
243    pub fn set_errors(control: &Control, errors: Option<Vec<BoxedValue>>) {
244        control.set_value(Self::errors_property(), errors);
245    }
246
247    /// Sets the errors of a control from the error of a binding.
248    pub fn set_error(control: &Control, error: Option<&BindingError>) {
249        Self::set_errors(
250            control,
251            Self::unpack_exception(error)
252                .map(|errors| errors.iter().filter_map(Self::unpack_data_validation_exception).collect()),
253        );
254    }
255
256    fn on_errors_or_converter_changed(control: &Control) {
257        let converter = Self::get_error_converter(control);
258        let original_errors = control.get_value(Self::original_errors_property());
259        let new_errors: Option<Vec<BoxedValue>> = match converter {
260            None => original_errors,
261            Some(converter) => {
262                original_errors.map(|errors| errors.iter().filter_map(|error| converter.convert(error)).collect())
263            }
264        };
265
266        let has_errors = new_errors.as_ref().is_some_and(|errors| !errors.is_empty());
267
268        control.set_current_value(Self::overriding_errors_internally_property(), true);
269
270        {
271            // Restores the flag when the scope ends, also by a panic of a handler.
272            struct Restore<'a>(&'a Control);
273
274            impl Drop for Restore<'_> {
275                fn drop(&mut self) {
276                    self.0.set_current_value(DataValidationErrors::overriding_errors_internally_property(), false);
277                }
278            }
279
280            let _restore = Restore(control);
281
282            control.set_current_value(Self::errors_property(), new_errors);
283        }
284
285        control.set_value(Self::has_errors_property(), has_errors);
286    }
287
288    /// Clears the errors of a control.
289    pub fn clear_errors(control: &Control) {
290        Self::set_errors(control, None);
291    }
292
293    /// Whether a control has errors.
294    pub fn get_has_errors(control: &Control) -> bool {
295        control.get_value(Self::has_errors_property())
296    }
297
298    /// Gets the error converter of a control.
299    pub fn get_error_converter(control: &Control) -> Option<ErrorConverter> {
300        control.get_value(Self::error_converter_property())
301    }
302
303    /// Sets the error converter of a control.
304    pub fn set_error_converter(control: &Control, converter: Option<ErrorConverter>) {
305        control.set_value(Self::error_converter_property(), converter);
306    }
307
308    /// The errors of an error: the inner errors of an aggregate or the error
309    /// itself, without the errors of the binding chain.
310    fn unpack_exception(exception: Option<&BindingError>) -> Option<Vec<BindingError>> {
311        let exception = exception?;
312        let inner = exception.inner();
313
314        let exceptions: Vec<BindingError> = if let Some(aggregate) = inner.downcast_ref::<AggregateError>() {
315            aggregate.inner_errors().to_vec()
316        } else if let Some(aggregate) = inner.downcast_ref::<AggregateException>() {
317            aggregate.inner_exceptions().iter().cloned().map(BindingError::new).collect()
318        } else {
319            vec![exception.clone()]
320        };
321
322        Some(exceptions.into_iter().filter(|x| !x.inner().is::<BindingChainException>()).collect())
323    }
324
325    /// The error data of a data validation error (`None` when it has none),
326    /// any other error as it is.
327    fn unpack_data_validation_exception(exception: &BindingError) -> Option<BoxedValue> {
328        if let Some(data_validation_exception) = exception.inner().downcast_ref::<DataValidationException>() {
329            return data_validation_exception.error_data().cloned();
330        }
331
332        let error: BoxedValue = Rc::new(exception.clone());
333        Some(error)
334    }
335}