Binding fields with BeanFieldGroup

Hello,

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?

Please help

Thanks in advance

Jonathan Poedts

Not sure if accidental typo only in example code or not, but in your code with annotations, you have “streename” instead of “streetname”.

Thank you for the reply,

Yes it is a typo, but only in the example I typed here, I didn’t copy paste my real code to make the example simpler.

I will correct the typo in the forum post

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.

The non-nested fields are working correctly. But the nested fields are not

I don’t see why it should not work. One of our tests is
https://github.com/vaadin/vaadin/blob/master/uitest/src/com/vaadin/tests/fieldgroup/FormWithNestedProperties.java
which tests this. Does it differ from your case in any way?

Hello,

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");
    }
}

Any news regarding my test case?

I had just the same problem. Binding for nested bean will work by switching “setItemDataSource” and “bindMemberFields”.

First bindMembers then add dataSource.

bean.bindMemberFields(testForm);
bean.setItemDataSource(testPerson);

Maybe it’s to late but I couldn’t find an answer any were else. So I past it here. “Try and Error” took some on my side. It’s not needed for others.

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.

Thanks a lot, I owe you a beer!

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?

Using 7.4.0.beta3

Sebastiaan

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);
}
}