See Vaadin Framework, Vaadin Data Model, Binding Data to Forms:
Conversions
You can also bind application data to a UI field component even though the types do not match.
Binder#bindInstanceFields()
says:
It's not always possible to bind a field to a property because their types are incompatible. E.g. custom converter is required to bind HasValue<String>
and Integer
property (that would be a case of "age" property). In such case IllegalStateException
will be thrown unless the field has been configured manually before calling the bindInstanceFields(Object)
method.
[...]: the bindInstanceFields(Object)
method doesn't override existing bindings.
[Emphases by me.]
So, AFAIU, this should work:
private final TextField siblingsCount = new TextField( "№ of Siblings" );
...
binder.forField( siblingsCount )
.withNullRepresentation( "" )
.withConverter(
new StringToIntegerConverter( Integer.valueOf( 0 ), "integers only" ) )
.bind( Child::getSiblingsCount, Child::setSiblingsCount );
binder.bindInstanceFields( this );
But it still throws:
java.lang.IllegalStateException: Property type 'java.lang.Integer' doesn't match the field type 'java.lang.String'. Binding should be configured manually using converter.
...
at com.vaadin.data.Binder.bindInstanceFields(Binder.java:2135)
...
Are you kidding me? That's what I did, didn't I? I rather doubt about "doesn't override existing bindings". Or, if not actually overridden, it seems they are ignored in bindInstanceFields()
, at least.
The same manual binding configuration works when not using Binder#bindInstanceFields()
but the approach with individual bindings for each field.
See also the thread Binding from Integer not working in the Vaadin Framework Data Binding forum and issue #8858 Binder.bindInstanceFields() overwrites existing bindings.
Workaround
Less convoluted than @cfrick's answer:
/** Used for workaround for Vaadin issue #8858
* 'Binder.bindInstanceFields() overwrites existing bindings'
* https://github.com/vaadin/framework/issues/8858
*/
private final Map<String, Component> manualBoundComponents = new HashMap<>();
...
// Commented here and declared local below for workaround for Vaadin issue #8858
//private final TextField siblingsCount = new TextField( "№ of Siblings" );
...
public ChildView() {
...
// Workaround for Vaadin issue #8858
// Declared local here to prevent processing by Binder#bindInstanceFields()
final TextField siblingsCount = new TextField( "№ of Siblings" );
manualBoundComponents.put( "siblingsCount", siblingsCount );
binder.forField( siblingsCount )
.withNullRepresentation( "" )
.withConverter( new StringToIntegerConverter( Integer.valueOf( 0 ), "integers only" ) )
.bind( Child::getSiblingsCount, Child::setSiblingsCount );
binder.bindInstanceFields( this );
...
// Workaround for Vaadin issue #8858
addComponent( manualBoundComponents.get( "siblingsCount" ) );
//addComponent( siblingsCount );
...
}
UPDATE
Fix #8998 Make bindInstanceFields not bind fields already bound using functions.
The source code for that fix appears at least in Vaadin 8.1.0 alpha 4 pre-release (and perhaps others).
Update by Basil Bourque…
Your idea, shown above, to use Binder::bindInstanceFields
after a manual binding for the non-compatible (Integer) property does indeed seem to be working for me. You complained that in your experimental code the call to Binder::bindInstanceFields
failed to follow the documented behavior where the call “doesn't override existing bindings”.
But it seems to work for me. Here is an example app for Vaadin 8.1.0 alpha 3. First I manually bind yearOfBirth
property. Then I use binder.bindInstanceFields
to bind the @PropertyId
annotated name
property. The field for both properties appear populated and respond to user-edits.
Did I miss something or is this working properly as documented? If I made a mistake, please delete this section.
package com.example.vaadin.ex_formatinteger;
import com.vaadin.annotations.Theme;
import com.vaadin.annotations.VaadinServletConfiguration;
import com.vaadin.data.Binder;
import com.vaadin.data.converter.StringToIntegerConverter;
import com.vaadin.server.VaadinRequest;
import com.vaadin.server.VaadinServlet;
import com.vaadin.ui.*;
import javax.servlet.annotation.WebServlet;
/**
* This UI is the application entry point. A UI may either represent a browser window
* (or tab) or some part of a html page where a Vaadin application is embedded.
* <p>
* The UI is initialized using {@link #init(VaadinRequest)}. This method is intended to be
* overridden to add component to the user interface and initialize non-component functionality.
*/
@Theme ( "mytheme" )
public class MyUI extends UI {
Person person;
//@PropertyId ( "honorific" )
final TextField honorific = new TextField ( "Honorific:" ); // Bean property.
//@PropertyId ( "name" )
final TextField name = new TextField ( "Full name:" ); // Bean property.
// Manually bind property to field.
final TextField yearOfBirthField = new TextField ( "Year of Birth:" ); // Bean property.
final Label spillTheBeanLabel = new Label ( ); // Debug. Not a property.
@Override
protected void init ( VaadinRequest vaadinRequest ) {
this.person = new Person ( "Ms.", "Margaret Hamilton", Integer.valueOf ( 1936 ) );
Button button = new Button ( "Spill" );
button.addClickListener ( ( Button.ClickEvent e ) -> {
spillTheBeanLabel.setValue ( person.toString ( ) );
} );
// Binding
Binder < Person > binder = new Binder <> ( Person.class );
binder.forField ( this.yearOfBirthField )
.withNullRepresentation ( "" )
.withConverter ( new StringToIntegerConverter ( Integer.valueOf ( 0 ), "integers only" ) )
.bind ( Person:: getYearOfBirth, Person:: setYearOfBirth );
binder.bindInstanceFields ( this );
binder.setBean ( person );
setContent ( new VerticalLayout ( honorific, name, yearOfBirthField, button, spillTheBeanLabel ) );
}
@WebServlet ( urlPatterns = "/*", name = "MyUIServlet", asyncSupported = true )
@VaadinServletConfiguration ( ui = MyUI.class, productionMode = false )
public static class MyUIServlet extends VaadinServlet {
}
}
And simple Person
class.
package com.example.vaadin.ex_formatinteger;
import java.time.LocalDate;
import java.time.ZoneId;
/**
* Created by Basil Bourque on 2017-03-31.
*/
public class Person {
private String honorific ;
private String name;
private Integer yearOfBirth;
// Constructor
public Person ( String honorificArg , String nameArg , Integer yearOfBirthArg ) {
this.honorific = honorificArg;
this.name = nameArg;
this.yearOfBirth = yearOfBirthArg;
}
public String getHonorific ( ) {
return honorific;
}
public void setHonorific ( String honorific ) {
this.honorific = honorific;
}
// name property
public String getName ( ) {
return name;
}
public void setName ( String nameArg ) {
this.name = nameArg;
}
// yearOfBirth property
public Integer getYearOfBirth ( ) {
return yearOfBirth;
}
public void setYearOfBirth ( Integer yearOfBirth ) {
this.yearOfBirth = yearOfBirth;
}
// age property. Calculated, so getter only, no setter.
public Integer getAge ( ) {
int age = ( LocalDate.now ( ZoneId.systemDefault ( ) )
.getYear ( ) - this.yearOfBirth );
return age;
}
@Override
public String toString ( ) {
return "Person{ " +
"honorific='" + this.getHonorific () + '\'' +
", name='" + this.getName () +
", yearOfBirth=" + this.yearOfBirth +
", age=" + this.getAge () +
" }";
}
}