Just for the sake of argument, I’d like to explore an alternative approach where Binder remains in charge of validation.
Core assumption: Component validation is a simple mechanism based on constraints and pre-defined validators. It uses string error messages from i18n, with the potential for default messages in the future. The order of validators is fixed and is not intended to be configurable. In contrast, Binder validation should be a flexible tool for handling anything that is more complex.
Problem: Binder currently lacks flexibility because it doesn’t offer a coherent approach to:
- Reusing component validators with error messages from i18n.
- Allowing custom error messages for component validators (String or ErrorMessageProvider).
- Adjusting component validators’ position, such as placing them after custom validators, for example.
- Disabling component validators completely while preserving their visual aspects
API idea: Below is an API concept that could address these limitations:
datePicker.setRequiredIndicatorVisible(true);
datePicker.setMin(0);
datePicker.setMax(100);
// Default
binder.forField(datePicker) // includes component validators with i18n error messages by default (required, bad input, min, max)
.withValidator(value -> value != LocalDate.now(), "Date must not be today")
.bind("date");
// Cross field validator + component validators in custom positions
binder.forField(datePicker)
.withValidator(dateOrAnotherFieldPredicate, "You must fill either one field or another")
.withComponentValidator(datePicker.getRequiredValidator()) // error message from i18n, .asRequired() will be alias that also enables setRequiredIndicatorVisible
.withComponentValidator(datePicker.getBadInputValidator()) // error message from i18n
.withComponentValidator(datePicker.getMinValidator()) // error message from i18n
.withComponentValidator(datePicker.getMaxValidator()) // error message from i18n
.bind("date");
// Custom error messages for component validators
binder.forField(datePicker)
.withValidator(dateOrAnotherFieldPredicate, "You must fill either one field or another")
.withComponentValidator(datePicker.getRequiredValidator(), "Field is required") // .asRequired("Field is required") will be alias that also enables setRequiredIndicatorVisible
.withComponentValidator(datePicker.getBadInputValidator(), "Invalid date")
.withComponentValidator(datePicker.getMinValidator(), "Date must be after 0")
.withComponentValidator(datePicker.getMaxValidator(), "Date must be before 100")
.bind("date");
// Disable component validators completely, but preserve visual aspect
binder.forField(datePicker)
.setComponentValidatorsDisabled(true) // This should be used with caution, as it disables the bad input validator too
.withComponentValidator(datePicker.getBadInputValidator()) // can still be added individually
.bind("date");
This approach implies that asRequired()
will become an alias for withComponentValidator(component.getRequiredValidator())
+ component.setRequiredIndicatorVisible(true)
, as follows:
Validator asRequired() {
component.setRequiredIndicatorVisible(true);
return withComponentValidator(component.getRequiredValidator())
}
Validator asRequired(String errorMessage) {
component.setRequiredIndicatorVisible(true);
return withComponentValidator(component.getRequiredValidator(), errorMessage)
}
With this, asRequired()
will work pretty much the same as before except that it will start using the error message from i18n (if available) in the case it was called without arguments.
So, to conclude, the idea is to leave components unchanged and instead enhance Binder API to support moving component validators and overriding their error message.