From bc88b8b0b2e6b88cdad79c3adfcfc8412d659946 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Johannes=20Dahlstr=C3=B6m?= Date: Thu, 21 Jul 2016 17:47:56 +0300 Subject: [PATCH] BoV: Components/Fields: Data binding and validation using Binder Change-Id: Iffc4a87bb907d68a1163774266401023ba7d644f --- .../components/components-fields.asciidoc | 326 ++++++------------ 1 file changed, 104 insertions(+), 222 deletions(-) diff --git a/documentation/components/components-fields.asciidoc b/documentation/components/components-fields.asciidoc index 173544dfe7..6e8d0c9c6a 100644 --- a/documentation/components/components-fields.asciidoc +++ b/documentation/components/components-fields.asciidoc @@ -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. -<> illustrates the inheritance relationships and the important interfaces and base classes. +user interface. <> 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 -<>. - - [[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 -<>. +Binder 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 <>, 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 <> 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 { @Override - public void buttonClick(ClickEvent event) { - field.setValidationVisible(false); - try { - field.validate(); - } catch (InvalidValueException e) { - Notification.show(e.getMessage()); - field.setValidationVisible(true); + public Result 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 +<>. -If the field is bound to a [classname]#FieldGroup#, described in -<>, 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[] -- 2.39.5