Question

Field validation works easy enough in Vaadin, but you can't use it to check for relations between fields (e.g. that a joining date must be before a leaving date), which is really annoying. So I added standard class level validation using JSR 303 which can do this. This works fine.

But I can perform this cross field validation only after I have commited the fields. That means that the bean already contains all field changes, and in case of a validation issue I need a possibility to "go back" to the valid state before the field commits (or to force somehow a "reload" of the bean), else I'm stuck with the changes, e.g. if the user decides to cancel the edit action.

Of course I could save all field contents before, and reset the state manually, but given that Vaadin does exactly the same in case of simple field validation I'd like to reuse that mechanism. But looking at the Vaadin code, I'm not very confident I can figure out what to do, and to do it right.

Please give me some hints how to deal with this problem without reinventing the wheel.

Was it helpful?

Solution

You can add a commit handler to your FieldGroup. This allows you to check before/after commitment:

binder.addCommitHandler(new CommitHandler() {

    @Override
    public void preCommit(CommitEvent commitEvent) throws CommitException {
        // TODO throw new CommitException() if your validation fails
    }

    @Override
    public void postCommit(CommitEvent commitEvent) throws CommitException {
        // TODO throw new CommitException() if your validation fails
    }
});

So it should be possible to "cross field" validation.

OTHER TIPS

You can try to create your own validator by implementing Validator interface:

final TextField field = new TextField("Name");
field.addValidator(new MyValidator());

class MyValidator implements Validator {
            @Override
            public void validate(Object value) throws InvalidValueException {
                if (!isValid(value))
                    throw new InvalidValueException("fail");
            }

            @Override
            public boolean isValid(Object value) {
                //some code
            }
}

Make sure that you use a com.vaadin.data.fieldgroup.FieldGroup for binding your edited data item (which you need to hold in a com.vaadin.data.Item instance). FieldGroup will manage the validation process for you; you only have to set the appropriate validators. FieldGroup is buffered (transactional) by default, so by either explicitly calling FieldGroup's discard() method or through a failed validation the original data of your edited item will be restored.

You can read about FieldGroups in the Book of Vaadin.

I ran into similar issue with Vaadin, especially when trying to run on the fly validation (without commiting) and when wanting to display field contextual errors. The issue is that when a field changes, its validator is called, but not the validators of the other fields. With cross field validation, a field could be validated/unvalidated when another field changes.

So, a solution is to manipulate the field's "componentError" member. In this example, the validation consists in ensuring that at least one of the two fields is provided:

    final Validator atLeastOneFieldValidator = new Validator() {
        @Override
        public void validate(final Object value) throws InvalidValueException {
            if (!crossValidate()) {
                throw new InvalidValueException("");
            }
        }
    };
    field1.addValidator(atLeastOneFieldValidator);
    field2.addValidator(atLeastOneFieldValidator);

My "crossValidate" method:

private boolean crossValidate() {
    if (Strings.isNullOrEmpty(field1.getValue()) && Strings.isNullOrEmpty(field2.getValue())) {
        final String error = "At least 1 field must be filled";
        field1.setComponentError(new UserError(error));
        field2.setComponentError(new UserError(error));
        return false;
    }
    field1.setComponentError(null);
    field2.setComponentError(null);
    return true;
}

But unfortunately this is not enough (and I cannot explain why), because when my two fields appear in error and I fill one of them, the error should disappear on both of them... but it only disappears from the one I modified. Maybe it's a bug in Vaadin. So I had to create a ValueChangeListener on the same model, in addition to the validator:

    final Property.ValueChangeListener atLeastOneField = new Property.ValueChangeListener() {
        @Override
        public void valueChange(final Property.ValueChangeEvent event) {
            crossValidate();
        }
    };
    field1.addValueChangeListener(atLeastOneField);
    field2.addValueChangeListener(atLeastOneField);

I don't know if there's better ways to do it, because it's full of workarounds. If someone has a better answer I would also like to see it! I post it as an answer although it raises new question.

While working on cross-validation, it is also important to remember that you can force re-validation on a field by simply marking it as dirty. So the following example will display validation for the current field and also force the stored Field to re-validate it's content. (I have not verified the effects of immediate/buffered on this strategy...but I don't see why those would have any negative impact on this solution)

Note: The following example is written in Groovy

private class EqualsFieldValidator implements Validator {

    final AbstractTextField field

    final String message

    public EqualsFieldValidator(AbstractTextField field, String message = 'fields are not equal') {
        this.field = field
        this.message = message
    }

    @Override
    void validate(Object value) throws InvalidValueException {
        field.markAsDirty()
        if (value != field.value) {
            throw new InvalidValueException(message)
        }
    }
}

This might be useful It isn't JSR-303, close to it but the differences are deliberate. Like JSR-303 it is driven by annotated fields and the annotations are mostly the same names. It does cross field validation using a background rules engine as well as simple validation (range checks etc) without the rules. (I know this is an old question but I stumbled on it just now)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top