Browse Source

BoV: Components/Fields: Data binding and validation using Binder

Change-Id: Iffc4a87bb907d68a1163774266401023ba7d644f
feature/eventbus
Johannes Dahlström 7 years ago
parent
commit
bc88b8b0b2
1 changed files with 104 additions and 222 deletions
  1. 104
    222
      documentation/components/components-fields.asciidoc

+ 104
- 222
documentation/components/components-fields.asciidoc View File

@@ -10,8 +10,8 @@ layout: page
((("[classname]#Field#", id="term.components.fields", range="startofrange")))

_Fields_ are components that have a value that the user can change through the
user interface.
<<figure.components.fields>> illustrates the inheritance relationships and the important interfaces and base classes.
user interface. <<figure.components.fields>> illustrates the inheritance relationships
and the important interfaces and base classes.

[[figure.components.fields]]
.Field components
@@ -69,114 +69,93 @@ guide.
The error message is set as the component error for the field and is usually
displayed in a tooltip when the mouse pointer hovers over the error indicator.

[[components.fields.databinding]]
== Data Binding and Conversions

Fields and selects can be coupled with business data objects with the [classname]#Binder# class.
Select components also allow management of the selectable items through the
[classname]#DataSource# interface. [classname]#Binder# and [classname]#DataSource#
can be thought of as bridges between the __presentation__ and __model__ architectural layers.

Fields are __editors__ for values of some particular type. For example,
[classname]#TextField# allows editing [classname]#String# values. When bound to
a data source, the type of the source property can be something different,
say an [classname]#Integer#. __Converters__ are used for converting the values
between the presentation and the model. They are described in
<<dummy/../../../framework/datamodel/datamodel-properties#datamodel.properties.converter,"Converting
Between Model and Presentation Types">>.


[[components.fields.valuechanges]]
== Handling Field Value Changes

[classname]#Field# inherits [classname]#Property.ValueChangeListener# to allow
listening for field value changes and [classname]#Property.Editor# to allow
editing values.

When the value of a field changes, a [classname]#Property.ValueChangeEvent# is
triggered for the field. You should not implement the
[methodname]#valueChange()# method in a class inheriting
[classname]#AbstractField#, as it is already implemented in
[classname]#AbstractField#. You should instead implement the method explicitly
by adding the implementing object as a listener.
[classname]#Field# provides two methods for listening to changes to the field value:
[methodname]#onValueChange# and [methodname]#addValueChangeListener#. The difference
is that the former takes a [interfacename]#Consumer# object that only receives the new value;
the latter, on the other hand, takes an [interfacename]#EventListener# that gets
a [classname]#ValueChange# event instance containing extra information about the event.

Both methods return a [classname]#Registration# object that can be used to later
remove the added listener if necessary.

[[components.fields.buffering]]
== Field Buffering
[source, java]
----
TextField textField = new TextField();
Label echo = new Label();

// Just echo in the label anything the user enters
textField.onValueChange(echo::setValue);

// Add a more complex listener
textField.addValueChangeListener(event -> {
String origin = event.isUserOriginated()
? "user"
: "application";
String message = origin
+ " entered the following: "
+ event.getValue();
Notification.show(message);
});
----

Field components implement the [interfacename]#Buffered# and
[interfacename]#BufferedValidatable# interfaces. When buffering is enabled for a
field with [methodname]#setBuffered(true)#, the value is not written to the
property data source before the [methodname]#commit()# method is called for the
field. Calling [methodname]#commit()# also runs validators added to the field,
and if any fail (and the [parameter]#invalidCommitted# is disabled), the value
is not written.
[[components.fields.databinding]]
== Binding Fields to Data

Fields can be grouped into _forms_ and coupled with business data objects with
the [classname]#Binder# class. When a field is bound to a property using
[classname]#Binder#, it gets its default value from the property, and
is stored to the property either manually via the [methodname]#Binder.save# method,
or automatically every time the value changes.

[source, java]
----
form.addComponent(new Button("Commit",
new Button.ClickListener() {
@Override
public void buttonClick(ClickEvent event) {
try {
editor.commit();
} catch (InvalidValueException e) {
Notification.show(e.getMessage());
}
}
}));
----
See the http://demo.vaadin.com/book-examples-vaadin7/book#component.field.buffering.basic[on-line example, window="_blank"].
class Person {
private String name;
public String getName() { /* ... */ }
public void setName(String) { /* ... */ }
}

Calling [methodname]#discard()# reads the value from the property data source to
the current input.
TextField nameField = new TextField();

If the fields are bound in a [classname]#FieldGroup# that has buffering enabled,
calling [methodname]#commit()# for the group runs validation on all fields in
the group, and if successful, all the field values are written to the item data
source. See
<<dummy/../../../framework/datamodel/datamodel-itembinding#datamodel.itembinding.buffering,"Buffering
Forms">>.
Binder<Person> binder = new Binder<>();

// Bind nameField to the Person.name property
// by specifying its getter and setter
binder.addField(nameField)
.bind(Person::getName, Person::setName);

[[components.fields.validation]]
== Field Validation
// Bind an actual concrete Person instance.
// After this, whenever the user changes the value
// of nameField, p.setName is automatically called.
Person p = new Person();
binder.bind(p;
----

The input for a field component can be syntactically or semantically invalid.
Fields implement the [interfacename]#Validatable# interface, which allows
checking validity of the input with __validators__ that implement the
[interfacename]#Validator# interface. You can add validators to fields with
[methodname]#addValidator()#.
== Validating Field Values

User input may be syntactically or semantically invalid.
[classname]#Binder# allows adding a chain of one or more __validators__ for
automatically checking the validity of the input before storing it to the data
object. You can add validators to fields by calling the [methodname]#addValidator#
method on the [interfacename]#Binding# object returned by [methodname]#Binder.addField#.

[source, java]
----
TextField field = new TextField("Name");
field.addValidator(new StringLengthValidator(
"The name must be 1-10 letters (was {0})",
1, 10, true));
field.setNullRepresentation("");
field.setNullSettingAllowed(true);
layout.addComponent(field);
binder.addField(nameField)
.addValidator(new StringLengthValidator(2, 20,
"Name must be between 2 and 20 characters long"))
.bind(Person::getName, Person::setName);
----
See the http://demo.vaadin.com/book-examples-vaadin7/book#component.field.validation.basic[on-line example, window="_blank"].

Failed validation is indicated with the error indicator of the field, described
in
Failed validation is indicated with the error indicator of the field, described in
<<dummy/../../../framework/application/application-errors#application.errors.error-indicator,"Error
Indicator and Message">>, unless disabled with
[methodname]#setValidationVisible(false)#. Hovering mouse on the field displays
the error message given as a parameter for the validator. If validated
explicitly with [methodname]#validate()#, as described later, the
[classname]#InvalidValueException# is thrown if the validation fails, also
carrying the error message. The value [literal]#++{0}++# in the error message
string is replaced with the invalid input value.

Validators validate the property type of the field after a possible conversion,
not the presentation type. For example, an [classname]#IntegerRangeValidator#
requires that the value type of the property data source is
[classname]#Integer#.
Indicator and Message">>. Hovering mouse on the field displays the error message
returned by the validator. If any value in a set of bound fields fails validation,
none of the field values are saved into the bound property until the validation
passes.

[[components.fields.validation.builtin]]
=== Built-in Validators
@@ -184,164 +163,67 @@ requires that the value type of the property data source is
Vaadin includes the following built-in validators. The property value type is
indicated.

[classname]#BeanValidator#::
Validates a bean property according to annotations defined in the Bean Validation API 1.0 (JSR-303).
This validator is usually not used explicitly, but they are created implicitly when binding fields in a [classname]#BeanFieldGroup#.
Using bean validation requires an implementation library of the API.
See <<dummy/../../../framework/datamodel/datamodel-itembinding#datamodel.itembinding.beanvalidation,"Bean Validation">> for details.

[classname]#CompositeValidator#::
Combines validators using logical AND and OR operators.
[classname]#RangeValidator#: [classname]#Comparable#::
Checks that the given [interfacename]#Comparable# value is at or between two given values.

[classname]#DateRangeValidator#: [classname]#Date#::
Checks that the date value is within the range at or between two given dates/times.
[classname]#StringLengthValidator#: [classname]#String#::
Checks that the length of the input string is at or between two given lengths.

[classname]#DoubleRangeValidator#: [classname]#Double#::
Checks that the double value is at or between two given values.
[classname]#RegexpValidator#: [classname]#String#::
Checks that the value matches the given regular expression.

[classname]#EmailValidator#: [classname]#String#::
Checks that the string value is a syntactically valid email address.
The validated syntax is close to the RFC 822 standard regarding email addresses.

[classname]#IntegerRangeValidator#: [classname]#Integer#::
Checks that the integer value is at or between two given values.

[classname]#NullValidator#::
Checks whether the value is or is not a null value.
+
For the validator to be meaningful, the component must support inputting null
values. For example, for selection components and [classname]#TextField#,
inputting null values can be enabled with [methodname]#setNullSettingAllowed()#.
You also need to set the representation of null values: in selection components
with [methodname]#setNullSelectionItemId()# and in [classname]#TextField# with
[methodname]#setNullRepresentation()#.

ifdef::web[]
+
Setting field as __required__ can be used for similar effect, and it also
enables an indicator to indicate that a value is required.
endif::web[]

[classname]#RegexpValidator#: [classname]#String#::
Checks that the value matches with the given regular expression.

[classname]#StringLengthValidator#: [classname]#String#::
Checks that the length of the input string is at or between two given lengths.

ifdef::web[]
+
The [parameter]#allowNull# parameter determines whether null values should be
allowed for the string, regardless of the string length. A null value has zero
length, so it will be invalid if the minimum length is greater than zero.
Allowing null value is meaningful only if inputting null values is enabled with
[methodname]#setNullSettingAllowed(true)#, and typically in such case, you want
to set the null representation to empty string with
[methodname]#setNullRepresentation("")#. Note that _this parameter is
deprecated_ and should normally be [parameter]#true#; then you can use
[methodname]#setRequired()# (for the false case) or [classname]#NullValidator#.
endif::web[]

Please see the API documentation for more details.

[[components.fields.validation.automatic]]
=== Automatic Validation

The validators are normally, when [literal]#++validationVisible++# is true for
the field, executed implicitly on the next server request if the input has
changed. If the field is in immediate mode, it (and any other fields with
changed value) are validated immediately when the focus leaves the field.


[source, java]
----
TextField field = new TextField("Name");
field.addValidator(new StringLengthValidator(
"The name must be 1-10 letters (was {0})",
1, 10, true));
field.setImmediate(true);
field.setNullRepresentation("");
field.setNullSettingAllowed(true);
layout.addComponent(field);
----
See the http://demo.vaadin.com/book-examples-vaadin7/book#component.field.validation.basic[on-line example, window="_blank"].


[[components.fields.validation.explicit]]
=== Explicit Validation

The validators are executed when the [methodname]#validate()# or
[methodname]#commit()# methods are called for the field.
=== Implementing Custom Validators

Validators implement the [interfacename]#Validator# interface that simply
extends [interfacename]#java.util.function.Function#, returning a special type
called [interfacename]#Result#. This return type represents the validation outcome:
whether or not the given input was valid.

[source, java]
----
// A field with automatic validation disabled
final TextField field = new TextField("Name");
field.setNullRepresentation("");
field.setNullSettingAllowed(true);
layout.addComponent(field);

// Define validation as usual
field.addValidator(new StringLengthValidator(
"The name must be 1-10 letters (was {0})",
1, 10, true));

// Run validation explicitly
Button validate = new Button("Validate");
validate.addClickListener(new ClickListener() {
class MyValidator implements Validator<String> {
@Override
public void buttonClick(ClickEvent event) {
field.setValidationVisible(false);
try {
field.validate();
} catch (InvalidValueException e) {
Notification.show(e.getMessage());
field.setValidationVisible(true);
public Result<String> apply(String input) {
if(input.length() == 6) {
return Result.ok(input);
} else {
return Result.error(
"Must be exactly six characters long");
}
}
});
layout.addComponent(validate);
}
----
See the http://demo.vaadin.com/book-examples-vaadin7/book#component.field.validation.explicit[on-line example, window="_blank"].


[[components.fields.validation.custom]]
=== Implementing a Custom Validator

You can create custom validators by implementing the [interfacename]#Validator#
interface and implementing its [methodname]#validate()# method. If the
validation fails, the method should throw either
[classname]#InvalidValueException# or [classname]#EmptyValueException#.

Because [methodname]#Result.ok# takes the valid value as an argument, a validator
can also do some sanitization on valid inputs, such as removing leading and
trailing whitespace from a string. Since [interfacename]#Validator# is a functional
interface, you can often simply write a lambda expression instead of a full class
declaration. There is also an [methodname]#addValidator# overload that creates a
validator from a boolean function and an error message.

[source, java]
----
class MyValidator implements Validator {
@Override
public void validate(Object value)
throws InvalidValueException {
if (!(value instanceof String &&
((String)value).equals("hello")))
throw new InvalidValueException("You're impolite");
}
}
binder.addField(nameField)
.addValidator(name -> name.length() < 20,
"Name must be less than 20 characters long")
.bind(Person::getName, Person::setName);

TextField field = new TextField("Say hello");
field.addValidator(new MyValidator());
field.setImmediate(true);
layout.addComponent(field);
----
See the http://demo.vaadin.com/book-examples-vaadin7/book#component.field.validation.customvalidator[on-line example, window="_blank"].

== Converting Field Values

[[components.fields.validation.fieldgroup]]
=== Validation in Field Groups
Field values are always of some particular type. For example,
[classname]#TextField# allows editing [classname]#String# values. When bound to
a data source, the type of the source property can be something different,
say an [classname]#Integer#. __Converters__ are used for converting the values
between the presentation and the model. Their usage is described in
<<dummy/../../../framework/datamodel/datamodel-properties#datamodel.properties.converter,"Converting
Between Model and Presentation Types">>.

If the field is bound to a [classname]#FieldGroup#, described in
<<dummy/../../../framework/datamodel/datamodel-itembinding#datamodel.itembinding,"Creating
Forms by Binding Fields to Items">>, calling [methodname]#commit()# for the
group runs the validation for all the fields in the group, and if successful,
writes the input values to the data source.

endif::disabled[]


Loading…
Cancel
Save