New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Server Side Validation is removed on blur #4804
Comments
Here is another example by @cdir. The invalid state gets overridden because of an extra validation round which is triggered by the client-side TextField field1 = new TextField();
TextField field2 = new TextField();
field1.addValueChangeListener(e -> {
field1.setInvalid(true);
field1.setErrorMessage("error");
field2.setInvalid(true);
field2.setErrorMessage("error");
});
add(field1);
add(field2);
|
We have the same issue in a CustomField that consists of a Select and a TextField. |
In general, we recommend using Binder for cross-field validation. It seems that @knoobie's example can be rewritten with Binder so that it won't require direct invalid state manipulations: @Route("example-bug")
public class ExampleBug extends Div {
public ExampleBug() {
var field1 = new ComboBox<>("Type", "A", "B", "C", "D");
var field2 = new ComboBox<>("Collection", "A", "B", "C", "D");
var field3 = new ComboBox<>("Version", "A", "B", "C", "D");
Binder<MyBean> binder = new Binder<>();
Validator<String> validator = (value, context) -> {
if ("A".equals(field1.getValue()) &&
"A".equals(field2.getValue()) &&
"A".equals(field3.getValue())) {
return ValidationResult.create("Notice: Combination of Fields is not recommended.", ErrorLevel.INFO);
}
return ValidationResult.ok();
};
var field1Binding = binder
.forField(field1)
.asRequired("Required")
.withValidator(validator)
.bind(MyBean::getField1, MyBean::setField1);
var field2Binding = binder
.forField(field2)
.asRequired("Required")
.withValidator(validator)
.bind(MyBean::getField2, MyBean::setField2);
var field3Binding = binder
.forField(field3)
.asRequired("Required")
.withValidator(validator)
.bind(MyBean::getField3, MyBean::setField3);
field1.addValueChangeListener(event -> {
if (field2.getValue() != null) {
field2Binding.validate();
}
if (field3.getValue() != null) {
field3Binding.validate();
}
});
field2.addValueChangeListener(event -> {
if (field1.getValue() != null) {
field1Binding.validate();
}
if (field3.getValue() != null) {
field3Binding.validate();
}
});
field3.addValueChangeListener(event -> {
if (field1.getValue() != null) {
field1Binding.validate();
}
if (field2.getValue() != null) {
field2Binding.validate();
}
});
binder.setBean(new MyBean("A", null, null));
var button = new Button("Validate after filling", e -> {
if (binder.validate().isOk()) {
System.out.println("All Good");
} else {
System.out.println("Binder not happy");
}
});
add(field1, field2, field3, button);
}
public static class MyBean {
private String field1;
private String field2;
private String field3;
public MyBean(String field1, String field2, String field3) {
this.field1 = field1;
this.field2 = field2;
this.field3 = field3;
}
public void setField1(String field1) {
this.field1 = field1;
}
public String getField1() {
return field1;
}
public void setField2(String field2) {
this.field2 = field2;
}
public String getField2() {
return field2;
}
public void setField3(String field3) {
this.field3 = field3;
}
public String getField3() {
return field3;
}
}
} @simasch @cdir @knoobie Do you have any use cases in mind that cannot be achieved with Binder? |
@vursen we cannot use Binder because we use our own binding framework. How does the binder set the validation, maybe we can use the same principle as a workaround? |
I can probably rewrite my code to rely "correctly" on Binder, but this feels like a big step backwards, huge breaking change and increases the already quite complex forms and binder combinations I'm using significant. For me the server-side is king. If the server decides that the field is invalid, it should stay like this until the server and all registered listener were asked again and say otherwise - this model is the main reason I'm using and relying on Vaadin Flow. Technically speaking, I'm not even manipulating the fields' state directly, but using the Binder already but probably not as intended ;) binder.getValidationErrorHandler().handleError(component,
ValidationResult.create("Notice: Combination of Fields is not recommended.", ErrorLevel.INFO)); So you are telling us, that the usage of special / public Binder features (like shown above), |
@knoobie , thank you for clarification!
The reason of why we are asking for use-cases is because we want to figure out what API could be added in order to achieve desired goals. |
Thanks for the additional insights @yuriy-fix! The problem with the Binder / adding those validations directly in the chain, comes from e.g. loose coupling and seperation of concern in complex applications. Especially those "info level" validations are more like a business requirement instead of e.g. normal technical requirement (not null / correct range) and are handled (in our case) way later in the form creation instead of the binder creation. We use for example a generic re-useable binder model and forms and only add those business requirement "validations" later once we know which form the binder and form is build for. At this stage of the circle, all BIndings are already finalized. For such use-case having an API to allow additional Validator in the finalized Binding are needed, which are currently not available (not even a Binder::getBinding(HasValue) method) and therefore no
Adding three listeners in my example fixes the issue above (replacing addValueChange with addClientValidated does not work 100% and creates flickering of error / not-error message depending on the action). Just to confirm: What is the recommended and long-term supported implementation? Do you recommended to find a way to fully use the binder or rely on the client validated events in conjunction with value change (which feels kinda brittle) listeners? |
Basically, there are two events that trigger Flow components to validate: Every field component internally listens to these events and runs constraint validation when they happen: Lines 140 to 142 in b973d4e
At the end of the constraint validation, the component updates its invalid state: Lines 471 to 473 in b973d4e
When binding Binder to a component, Binder adds its own listeners to those two events on top of the component ones, which leads to the following sequence:
Binder updates the component's invalid state based on the chain of validators which includes
In your case – where you have a totally custom binder implementation – the current workaround would be to make sure you run your custom validation on both public class ExampleView extends Div {
TextField textField;
public ExampleView() {
textField = new TextField();
textField.setMinLength(10);
textField.setMaxLength(20);
textField.addValueChangeListener(event -> validate());
textField.addClientValidatedEventListener(event -> validate());
add(textField);
}
private void validate() {
String value = textField.getValue();
ValidationResult constraintValidationResult = textField
.getDefaultValidator()
.apply(value, null);
// Check for the constraint validation result
if (constraintValidationResult.isError()) {
textField.setInvalid(true);
return;
}
// Your custom validation rules
if ("invalid".equals(textField.getValue())) {
textField.setInvalid(true);
return;
}
textField.setInvalid(false);
}
} |
@vursen I think that is no option. Our own binding works like this:
In the value change listener I only have the option to call the 'model change event'. Every model change event will validate the whole business model because we have quite a lot validations which refer to more complex state of the whole model. Assuming I call my model change event in valueChanged and clientValidationEvent, my own model change event is called several times. When I get the point of 'client validation event' correctly it is triggered every time a value changed in the client. That means I get at least two 'model change events'. Additionally there might be other fields that get updated when changing a value. For every updated field I get an additional event. |
Is there any updates on this issue? We are discovering more and more weird constellations caused by the discrepancy between the valid state in the UI and the model, especially in combination with required state. It is becoming increasingly confusing for our users when a field is marked as invalid/valid and why. Our work around right now is basically to store the validation messages and the severity level (error = invalid) in a separate attribute, and completely ignore the client validation result. This feels like a really hacky "solution". Similar to @knoobie s comment, our validations are all business requirements, including the required fields. The only technical validations are formatting validations. Ideally, our UI component should reflect the state of the business model at all time. In my opinion, client validation may add to server validations, but never remove or override any. |
We are looking into options for providing a solution for these custom validation use cases. |
Dear @knoobie, Dear @cdir,
With getting result from Dear @leluna, |
Hi @yuriy-fix , thanks for the helpful reply! I think being able to disallow the field to set invalid state would be exactly what we need. I think #16420 would probably also solve the issue, if we could remove the default validator at a later point? The problem with overriding the validate method is that we would have to extend all components. Currently, we are mostly using the components as is. |
Hi @yuriy-fix, as @leluna mentioned, overriding every component we use is not an acceptable solution. I think it should be possible to disable client side validation. I am even more confused by
With this, I would expect to get no validation from the field. However, the field actually sets the invalid flag and validates that it should not be empty. Right now, we only have the option to write our own validation message to the field through a second property and read it in an additional ClientValidatedEventListener to set it again. Not a satisfactory situation. I also noticed that in a TextField, this listener is called on every keystroke when the field is invalid. In a ComboBox, on the other hand, the listener is not called when I set a value but only when I exit the field. This also seems to be inconsistent and leads to many unnecessary requests, especially in the TextField. |
From my point of view, this is a bug or regression with Vaadin 24. With Vaadin 23, we could easily run a validation in the business logic and write to the field. Now we need ugly workarounds to achieve the same behavior which also affect the performance (although not much). Either fix this or implement #5074 which seems to be a reasonable solution. |
Hey! I'm here to inform you that we've introduced a manual validation mode that disables the component's built-in validation, allowing you to have fully manual control over the component's invalid state and error messages. The feature will be included in the upcoming The idea is that you simply enable this mode for your existing fields and it will prevent all the conflicts and overrides described in this ticket: TextField textField = new TextField();
textField.setManualValidation(true);
textField.addValueChangeListener(event -> {
// Run your custom validation logic on ValueChangeEvent as before
}); Feel free to reopen the ticket or create a new one if the issue hasn't been resolved for you. |
Description
Using the server side API to add validation errors on fields is overwritten once the user blurs the field.
Expected outcome
All fields are still shown as invalid and error message is present even when user entered and left a field.
Minimal reproducible example
Steps to reproduce
Add this class to a view
Select 'A', 'A', 'A' --> everything is good (matching the check / recommendation)
Select 'B' somewhere --> all fields should show an error now (working!)
Blur the field where you changed the value to 'B' --> field is instantly marked as correct, even tho the server side wasn't asked / notified again (where the check would say: nope; still bad)
Environment
Vaadin version(s): 24.0.0
Browsers
Firefox
The text was updated successfully, but these errors were encountered: