]> source.dussan.org Git - vaadin-framework.git/commitdiff
BoV: Components/Fields: Data binding and validation using Binder
authorJohannes Dahlström <johannesd@vaadin.com>
Thu, 21 Jul 2016 14:47:56 +0000 (17:47 +0300)
committerTeemu Suo-Anttila <teemusa@vaadin.com>
Fri, 22 Jul 2016 12:42:38 +0000 (12:42 +0000)
Change-Id: Iffc4a87bb907d68a1163774266401023ba7d644f

documentation/components/components-fields.asciidoc

index 173544dfe7d581cb9a489c9f99c3e71e3ed1f51a..6e8d0c9c6ab6a01d0d40856126b369b570ab6c51 100644 (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[]