Open obfischer opened 2 years ago
@obfischer thank you for creating an issue!
Currently I add validators via Binder#withValidator() to the binder before I all Binder#bindInstanceFields()
could you please give a code example to us showing how you're doing it currently, just for making it clear for us how could an API for cross-field validation be looks like.
Any idea when this ticket will eventually be picked up? I mean, bean validation at class level is something very common... I guess almost everybody using JSR-303 bean validations uses class level annotations.
@sandronm We have no plans to pick it in the nearest future, as well as no plans to include it into V23.3. Vaadin 24 would be a good candidate to have this feature.
What would this feature look like in practice?
If the bean has two separate validators related to different fields, then what would you expect to write in application code to help the binder understand where each validation error should be shown?
What would this feature look like in practice?
If the bean has two separate validators related to different fields, then what would you expect to write in application code to help the binder understand where each validation error should be shown?
On top of your form. It will be indeed difficult to directly mark as invalid any input (textfield, checkbox, ...).
Everything exists to make this work other than catching the class level constraint violation bit. Here's what I have:
entityBinder.withValidator(new ClassValidator());
entityBinder.setStatusLabel(errorText);
...
private static class ClassValidator extends AbstractValidator<Attribute> {
protected ClassValidator() {
super("Class level validator(s) failed");
}
@Override
public ValidationResult apply(Attribute value, ValueContext context) {
var violations = validator.validate(value);
for (var violation : violations) {
var path = violation.getPropertyPath();
/*
* Property level violations will be handled by the binder
*/
if (path != null && !path.toString().isBlank()) {
continue;
}
/*
* There's (probably) only one class level constraint, so just
* return the first found.
*/
return ValidationResult.error(violation.getMessage());
}
return ValidationResult.ok();
}
}
I was asking specifically for the case from the original ticket description:
I need a way to relate a given constraint violation to one or more fields in my forms, so that I can highlight it and the user which fields are not valid.
The need to define a custom class like the ClassValidator
example is also a known limitation, but that's a slightly different issue and one for which a workaround does exist.
My question was about ideas for how to design an API that allows that different class-level constraint messages should be shown as error labels for different form fields.
As an extreme example, imagine that we've got a form with two text fields, textField1
and textField2
and a bean with three different class-level constraints:
@OneClassConstraint
@RepeatedClassConstraint("foo")
@RepeatedClassConstraint("bar")
public class MyBean {
// Some fields, setters and getters here as well
}
If we want errors from @OneClassConstratint
to be shown for textField1
, errors from @RepeatedClassConstraint("foo")
shown for textField2
and @RepeatedClassConstraint("bar")
in a binder level status label (i.e. Binder::setStatusLabel
), then what could the optimal application code look like to configure the binder in that way if we have full freedom to make changes to the API of BeanValidationBinder
?
I have the same issue.
The basic problem is this: there are many examples of field-level validation logic where the determination of "valid" depends on other fields as well as the field being validated.
A simple example is a "Confirm password" field, where you have a "Password" field and a "Confirm password" field. The "Confirm password" field has the following field-level validation constraint: This field's value must be the same as the "Password" field's value.
While this validation constraint does involve multiple fields, it is intuitively "field-level" because when the constraint is violated, there is only one field that is "wrong" (namely, the "Confirm password" field) and moreover you want the validation error to appear next to that field.
There is a simple & easy way we could accomplish this with minimal change to the current validation API: Add a new method ValueContext.getBinder()
which returns the Binder
associated with the validation operation being performed.
Then the "Confirm password" problem is easily solved like this:
public class ConfirmPasswordValidator implements Validator<String> {
@Override
public ValidationResult apply(String confirmation, ValueContext context) {
PasswordField passwordField = context.getBinder().getBinding("password").get().getField();
if (!Objects.equals(confirmation, passwordField.getValue()))
return ValidationResult.error("Passwords must match"));
return ValidationResult.ok();
}
}
FYI, added this as a separate feature request #19060
Note that the ConfirmPasswordValidator
in itself is not enough for the use case of a field that is validated based on the value of some other field. In addition, you also need to invalidate the validation status for the confirm field when the value of the password field changes.
Good point - though that's not an issue if, as in my case, you call binder.setFieldsValidationStatusChangeListenerEnabled(false)
to defer validation until the form is submitted.
In fact, the two kind of go hand-in-hand.
Describe your motivation
Some of my domain objects have constraints between different fields. For example if value a > 10, value b must be given etc. etc. To express such constraints with the help of Java Bean Validation is possible. For such cases, I can to write custom constraints on class level.
Unfortunately I don't know a clean way to relate such failing constraints to the fields in my forms.
Describe the solution you'd like
I need a way to relate a given constraint violation to one or more fields in my forms, so that I can highlight it and the user which fields are not valid.
Describe alternatives you've considered
Currently I add validators via
Binder#withValidator()
to the binder before I allBinder#bindInstanceFields()
.Additional context
It would be great to have only one approach for validation, which can be used for the frontend as well as for the backend.