Binder not updating bean in validation

Hi, having an issue with the bean validation not showing the updated bean in my withValidation method. All seems to work when everything is valid but when a validation fails it doesn’t pick up my checkbox boolean value.

Example Issue:
I have 5 text-fields that must have text to be valid. I also have a checkbox that when selected displays more text-fields with the validation to check binder.getBean.isMoreFieldsSelected() this boolean is bound to that checkbox. This all works nicely and updates when all the fields are filled out and when the checkbox gets deselected. If I remove the text from the fields that display when the checkbox is selected it will show the validation errors which it is suppose to do. The issue now starts when I deselect the box, the binder bean in the withValidation() has not got the updated boolean value bound to the checkbox, therefore, im unable to complete the form I have created. note: This works when those extra text-fields pass the validation it is only when they don’t it breaks.

Validation method for extra text fields:

binder.forField(extraCB)
                .bind(Object::isMoreFieldsSelected,Object::setMoreFieldsSelected);
binder.forField(textField1)
                .withValidator((Validator<String>) (value, context) -> {
                    if(binder.getBean().isMoreFieldsSelected()) {
                        if(value.length() > 0) {
                            return ValidationResult.ok();
                        }
                        return ValidationResult
                                .error("Must be set if more fields checkbox is selected");
                    }else{
                        return ValidationResult.ok();
                    }
                })
				.bind(Object::getExtraText, Object::setExtraText);

Why would this happen? Im using Vaadin 14.1.3

Hi, can you create a standalone example that reproduces this broken behavior?

I’ve added this code to one of your exercises and it causes the same problem. The binder doesn’t update when something fails validation.

@Route(value = BackEndDataProvider.ROUTE, layout = MainLayout.class)
public class BackEndDataProvider extends VerticalLayout {
	private static final long serialVersionUID = 1L;

	public static final String ROUTE = "ex3";
	public static final String TITLE = "Binder Test";

	public BackEndDataProvider() {
 		setWidth("100%");
                
                TextField textField1 = new TextField();
                Checkbox checkbox = new Checkbox();
                TextField textField2 = new TextField();
                Binder<TestObject> binder = new Binder<>();
                Button submitButton = new Button();
                Button beanToString = new Button();
                Paragraph p = new Paragraph();
                
                textField1.setLabel("Textfield 1");
                textField2.setLabel("TextField 2");
                submitButton.setText("Submit");
                beanToString.setText("Output bean variables");
                
                submitButton.setEnabled(false);
                textField2.setVisible(false);
                
                TestObject testObject = new TestObject();
                
                binder.forField(textField1)
                        .withValidator((Validator<String>) (value, context) -> {
                            if(value.length() > 0) {
                                return ValidationResult.ok();
                            }
                            return ValidationResult
                                    .error("Must be set");
                        })
                        .bind(TestObject::getText1, TestObject::setText1);
                binder.forField(checkbox)
                    .bind(TestObject::isMoreFields,TestObject::setMoreFields);
                binder.forField(textField2)
                    .withValidator((Validator<String>) (value, context) -> {
                        if(binder.getBean().isMoreFields()) {
                            if(value.length() > 0) {
                                return ValidationResult.ok();
                            }
                            return ValidationResult
                                    .error("Must be set if more fields checkbox is selected");
                        }else{
                            return ValidationResult.ok();
                        }
                    })
                    .bind(TestObject::getText2, TestObject::setText2);
                
                binder.setBean(testObject);
                
                binder.addStatusChangeListener(e -> {
                    if(binder.isValid()) {
                        submitButton.setEnabled(true);
                    }else{
                        submitButton.setEnabled(false);
                    }
                });
                binder.addValueChangeListener(e -> {
                    if(binder.isValid()) {
                        submitButton.setEnabled(true);
                    }else{
                        submitButton.setEnabled(false);
                    }
                });
                
                checkbox.addValueChangeListener(e -> {
                    textField2.setVisible(e.getValue());
                });
                
                beanToString.addClickListener(e -> {
                    p.setText(binder.getBean().toString());
                });
                
                add(textField1, checkbox, textField2, submitButton, beanToString, p);
	}
}


public class TestObject {
    private String text1;
    private boolean moreFields;
    private String text2;

    public String getText1() {
        return text1;
    }

    public void setText1(String text1) {
        this.text1 = text1;
    }

    public boolean isMoreFields() {
        return moreFields;
    }

    public void setMoreFields(boolean moreFields) {
        this.moreFields = moreFields;
    }

    public String getText2() {
        return text2;
    }

    public void setText2(String text2) {
        this.text2 = text2;
    }

    @Override
    public String toString() {
        return "TestObject{" + "text1=" + text1 + ", moreFields=" + moreFields + ", text2=" + text2 + '}';
    }

This code should get you a working example. If you enter text in textfield1, click the checkbox and enter text into textfield2, delete that text CLICK on a blank space to allow the red validation text to display and unselect the checkbox the submit button does not become enabled. If you click the output bean it still shows the old text and old boolean value before validation failed. It looks like it is intended not to make changes to the bean if validation fails but what if the validation has a knock on affect to other textbox validation? I had to change my code to check the checkbox.getValue() rather than getting the binder bean as it doesn’t update.

Ah, is the key:

The binder doesn’t update when something fails validation.

This is by design - by default, invalid values should never enter the model. There’s an open pull request to add a feature to disable or re-enable validation, but it’s not yet complete: https://github.com/vaadin/flow/pull/8094

I kinda understand that but shouldn’t the boolean change which is linked to the checkbox? The checkbox has no validation against it and it does not change, so it seems the bean is unchanged when validation fails rather than the variable within the bean itself. It sorta leaves the whole bean in a locked state and until the validation has been resolved which causes issues for fields which are valid.