I am trying out Vaadin 7 and I try to make a form that can be used to create a new Person.
The person has an adres that is it’s own object.
I make use of a FormLayout in order to build this, and I use a BeanFieldGroup to bind the fields on the screen with the properties of the person.
When doing bindMemberFields on the BeanFieldGroup only the direct field are bound. Not the fields comming from adres.
In the declaration of this fields I used @PropertyId in order to set the correct property mapping
e.g.
@PropertyId("adres.streetname")
TextField street
This does not work.
Howhever when I’m binding manualy
e.g
myBean.bind(street, "adres.streetname")
The binding works correctly.
Is this a bug? Or am I using the PropertyId wrong?
Otherwise it seems fine. Are non-nested fields working correctly, like @PropertyId(“name”) or something like that? Remembered to call fieldGroup.bindMemberFields(object)? I’m just going trough some basic cases as I can’t really figure out what’s wrong. I haven’t used the combination of binding member fields and nested classes, but as far as I know, it should work.
Thank you for you answer.
After taking a first look I do not see any difference between the code you provided and my code. I will copy paste some code to show this.
The declaration of the field:
@PropertyId(Individu.PROP_OFFICIEELADRES + "." + Adres.PROP_STRAATNAAM)
public TextField straatnaam;
fyi:
Individu.PROP_OFFICIEELADRES + “.” + Adres.PROP_STRAATNAAM ==> officieelAdres.straatnaam
Where I do the binding
myBean = new BeanFieldGroup<Individu>(Individu.class);
myBean.setItemDataSource(getNewIndividu());
myBean.bindMemberFields(this);
But this doesn’t cut it, to be able to continue developing I created my own binding function, when using it it does work indeed.
How I made binding work in my case
public static void bindRel(BeanFieldGroup beanFieldGroup, AbstractComponentContainer component) {
for (Field f : component.getClass().getFields()) {
com.vaadin.ui.Field c;
try {
c = (com.vaadin.ui.Field<?>) ReflectTools.getJavaFieldValue(component, f, com.vaadin.ui.Field.class);
PropertyId a = f.getAnnotation(PropertyId.class);
if (c != null && a != null)
beanFieldGroup.bind(c, a.value());
} catch (Exception e) {
// If we cannot determine the value, just skip the field and try
// the next one
}
}
}
Maybe I forget something but I really don’t see what.
To help me show my case I took the liberty to write a unit test that shows where the behaviour fails.
First we have a TestPerson and an TestAdres class
public class TestPerson{
private String name;
private String firstName;
private TestAdres adres;
public TestAdres getAdres() {
return adres;
}
public void setAdres(TestAdres adres) {
this.adres = adres;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
}
public class TestAdres {
private String streetName;
public String getStreetName() {
return streetName;
}
public void setStreetName(String streetName) {
this.streetName = streetName;
}
}
And then the testcase itself, the first part works like a charm, because I do not use nested properties, but the second fails (only when I use my own binding function the binding gets done.
public class BindingTest {
public class TestForm extends FormLayout {
@PropertyId("name")
public TextField nameField;
@PropertyId("firstName")
public TextField firstNameField;
public TestForm() {
nameField = new TextField("Name");
firstNameField = new TextField("First name");
addComponent(nameField);
addComponent(firstNameField);
}
}
public class TestFormExtended extends TestForm {
@PropertyId("adres.streetName")
public TextField streetField;
public TestFormExtended() {
super();
streetField = new TextField("Street");
}
}
@Test
public void testBindSimple() {
TestPerson testPerson = new TestPerson();
testPerson.setFirstName("John");
testPerson.setName("Doe");
TestForm testForm = new TestForm();
BeanFieldGroup<TestPerson> bean = new BeanFieldGroup<TestPerson>(TestPerson.class);
bean.setItemDataSource(testPerson);
Assert.assertEquals(bean.getBoundPropertyIds().size(), 0);
bean.bindMemberFields(testForm);
Assert.assertEquals(bean.getBoundPropertyIds().size(), 2); //Adres still unbound;
}
@Test
public void testBindExtended() {
TestPerson testPerson = new TestPerson();
testPerson.setFirstName("John");
testPerson.setName("Doe");
TestAdres adres = new TestAdres();
adres.setStreetName("StreetName");
testPerson.setAdres(adres);
TestFormExtended testForm = new TestFormExtended();
BeanFieldGroup<TestPerson> bean = new BeanFieldGroup<TestPerson>(TestPerson.class);
bean.setItemDataSource(testPerson);
Assert.assertEquals(bean.getBoundPropertyIds().size(), 0);
//Check
Assert.assertNotEquals(testForm.firstNameField.getValue(), "John");
Assert.assertNotEquals(testForm.nameField.getValue(), "Doe");
bean.bindMemberFields(testForm);
Assert.assertEquals(bean.getBoundPropertyIds().size(), 2); //Adres still unbound while it shouldn't;
Assert.assertEquals(testForm.firstNameField.getValue(), "John");
Assert.assertEquals(testForm.nameField.getValue(), "Doe");
//**********************************Here comes the weird stuff**********************************************
//**********************************************************************************************************
//This assertion fails while it shouldn't
Assert.assertEquals(testForm.streetField.getValue(), "StreetName");
VaphVaadinUtil.bindRel(bean, testForm);
//After my own binding
Assert.assertEquals(bean.getBoundPropertyIds().size(), 3); //Adres streetname is bound;
Assert.assertEquals(testForm.streetField.getValue(), "StreetName");
Assert.assertEquals(testForm.nameField.getValue(), "Doe");
Assert.assertEquals(testForm.firstNameField.getValue(), "John");
}
}
Oh, man! you saved my day. I already lost my hair going through vaadin source trying to figure out why it fails to bind nested properties.
It is interesting that many developers make same mistake and Vaadin book is scarce on this topic.
Had a hard time with this as well, but I also had a field that was called “description” that one did not work (null), I changed it to info in the form, design and model. Is Description a reserved word?
i’m stuck on this exercise for 3 days now, dont know y it is not working… i guess something’s wrong with binding member fields.
@SuppressWarnings(“serial”)
public class ProductForm extends CustomComponent implements View {
public ProductForm() {
HorizontalLayout horizontalLayout = new HorizontalLayout();
horizontalLayout.setSizeFull();
setCompositionRoot(horizontalLayout);
final Item item = createItem();
horizontalLayout.addComponent(createEditLayout(item));
horizontalLayout.addComponent(createViewLayout(item));
}
public Layout createEditLayout(Item item) {
// TODO Create a layout for editing the product.
FormLayout formLayout = new FormLayout();
final FieldGroup binder = new FieldGroup(item);
binder.bindMemberFields(this);
TextField name = new TextField(“name”);
TextField price = new TextField(“price”);
OptionGroup options = new OptionGroup(“options”);
options.setMultiSelect(true);
options.addItems(“First”, “Second”,“Third”);
DateField available = new DateField(“avalible”);
available.setDateFormat(“dd-MM-yyyy”);
available.setValue(new Date());
// TODO Create Save and Cancel buttons which will commit/discard the
HorizontalLayout footer = new HorizontalLayout();
footer.setSpacing(true);
Button save = new Button(“save”);
save.addClickListener(new Button.ClickListener() { @Override
public void buttonClick(ClickEvent event) {
// TODO Auto-generated method stub
try {
binder.commit();
}
catch (InvalidValueException e) {
}
catch (CommitException e) {
}
}
});
Button discard = new Button(“discard”);
discard.addClickListener(new Button.ClickListener() { @Override
public void buttonClick(ClickEvent event) {
// TODO Auto-generated method stub
binder.discard();
}
});
footer.setSpacing(true);
footer.setMargin(true);
footer.addComponent(save);
footer.addComponent(discard);
// values in the FieldGroup.
//
formLayout.setMargin(true);
formLayout.setSpacing(true);
formLayout.setSizeFull();
formLayout.addComponent(name);
formLayout.addComponent(price);
formLayout.addComponent(options);
formLayout.addComponent(available);
formLayout.addComponent(footer);
return formLayout;
}
public Layout createViewLayout(Item item) {
FormLayout layout = new FormLayout();
Label name = new Label();
// TODO Bind the name property to this label
name.setCaption(“Name”);
name.setPropertyDataSource(item.getItemProperty(“name”));
layout.addComponent(name);
Label price = new Label();
price.setCaption(“Price”);
// TODO Bind the price property to this label
price.setPropertyDataSource(item.getItemProperty(“price”));
layout.addComponent(price);
Label options = new Label();
options.setCaption(“Options”);
// TODO Bind the options property to this label. Since options is a Set,
// you’ll need to use the collection converter provided.
options.setConverter(new CollectionToStringConverter());
options.setPropertyDataSource(item.getItemProperty(“options”));
layout.addComponent(options);
Label available = new Label();
available.setCaption(“Available”);
// TODO Bind the available property to this label
available.setPropertyDataSource(item.getItemProperty(“available”));
layout.addComponent(available);
final FieldGroup binder = new FieldGroup(item);
binder.bindMemberFields(this);
return layout;
} @Override
public void enter(ViewChangeEvent event) {
}
private static Item createItem() {
Product product = new Product();
product.setName(“”);
product.setOptions(new HashSet(Arrays.asList(“”)));
product.setAvailable(Calendar.getInstance().getTime());
return new BeanItem(product);
}
}