Simple Field Validation
Whether you are coming from an older version of Vaadin Framework, or you are new to Vaadin, there is a good chance that you want to validate fields without using Binder
.
Using binder is a useful way to manage your application data layer, but there are still cases where you want a quick and straightforward one-line validator. This tutorial shows you how to create a helper class to get the classical one line validation on a field. The example here uses TextField
, but it should apply to any other field.
Show me the code
public class ValidTextField extends TextField {
class Content {
String content;
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
private Content content = new Content();
private Binder<Content> binder = new Binder<>();
private List<Validator<String>> validators = new ArrayList<>();
public ValidTextField() {
binder.setBean(content);
}
public void addValidator(
SerializablePredicate<String> predicate,
String errorMessage) {
addValidator(Validator.from(predicate, errorMessage));
}
public void addValidator(Validator<String> validator) {
validators.add(validator);
build();
}
private void build() {
BindingBuilder<Content, String> builder =
binder.forField(this);
for(Validator<String> v: validators) {
builder.withValidator(v);
}
builder.bind(
Content::getContent, Content::setContent);
}
}
You can also find the source code on github. Add the class into your project and use it as following:
ValidTextField valid = new ValidTextField();
valid.addValidator(
new StringLengthValidator("Wrong length", 1, 10));
valid.addValidator(
s -> s.equals("Vaadin"), "Not Vaadin!");
In case you want to have the same functionality for a different field, change the class to extend the desired field instead of TextField
.
How it works
The trick used in this helper class is to include an elementary POJO class Content
, and built-in binder to do the data binding behind the scene.
The constructor assigns the default bean to the binder binder.setBean(content);
, and what we need in the addValidator
is to include the new validator:
binder.forField(this)
.withValidator(v)
.bind(Content::getContent, Content::setContent);
The code above would work perfectly if we have only one validator assigned. However, since we expect more than one, and we can not add new validators after the bind
statement, the second trick is to keep track of all the requested validators and re-assign them whenever we call the addValidator
method. To do that, we keep a member variable private List<Validator<String>> validators
that holds all requested validators, and we iterate over them to make sure all provided validators apply on the field:
public void addValidator(Validator<String> validator) {
validators.add(validator);
build();
}
private void build() {
BindingBuilder<Content, String> builder =
binder.forField(this);
for(Validator<String> v: validators) {
builder.withValidator(v);
}
builder.bind(
Content::getContent, Content::setContent);
}
Going advanced
As stated earlier, simple validation should be used in straightforward cases. However, there is always room to make it a bit more generic.
For example, the earlier examples assumed that the TextField
always has a String
value. However, this is not always the case. We can solve this by:
Create a
TextFieldType
class that extendsValidTextField
, and perform the conversion between the desiredType
andString
in theTextFieldType
.Change
ValidTextField
to extends a customTextFieldType
and change the code to supportType
instead ofString
.Use generics as a parameter to our custom implementation:
public class GenericTextField<T> extends TextField {
}
The above code follows a few other changes:
a. Content
class should be of type T
instead of String
.
b. A converter must be provided to specify how to convert from T
to the default type of the Textfield
which is String
. The converter can be specified in the constructor, and a default converter (in case T
is String
) can be applied.
This class has a reference implementation that covers those cases, as well as general use of converters.
GenericTextField<Integer> generic = new GenericTextField<>(
Integer::valueOf,
i -> i==null? "0" : String.valueOf(i) ,
"Can't convert between String and Integer"
);
generic.addValidator(
new IntegerRangeValidator("Not within range", 0, 3));
There is also a simple class to use in case of type is String
.
SimpleTextField simple = new SimpleTextField();
simple.addValidator(
new StringLengthValidator("Min 3 chars required", 3));
Next steps
The GenericTextField
has a more generalized way to deal with different sort of field values, and it also comes with a similar approach to add converters inline. However, there are a few limitations:
Unlike validators, converters are tricky because they need to execute in a specific order, and they need to be applied only once because they may involve changes in the data model. The current demo does not cover those complex cases.
The demo here uses
TextField
all the time. The demo is not generalized to anyAbstractField
, but for now, you can change the superclass to a different field.Remove validators or converters is not covered since I couldn’t find a proper use case.
Let’s discuss those items in the comments section below and enhance those reference implementations based on the discussion.
Comments (0)