Creating a CustomField for editing the address of a person

A normal use case is that you want to create a form out a bean that the user can edit. Often these beans contain references to other beans as well, and you have to create a separate editor for those. This tutorial goes through on how to edit an Address bean which is inside a Person bean with the use of CustomField and FieldGroup.

Here are the Person and Address beans

public class Person {
  private String firstName;
  private String lastName;
  private Address address;
  private String phoneNumber;
  private String email;
  private Date dateOfBirth;
  private String comments;

  //Getters and setters
}
public class Address {
  private String street;
  private String zip;
  private String city;
  private String country;

  // Getters and setters
}

Creating a new field

The first step is to create a new field which represents the editor for the address. In this case the field itself will be a button. The button will open a window where you have all the address fields. The address will be stored back when the user closes the window.

public class AddressPopup extends CustomField<Address> {
  @Override
  protected Component initContent() {
    return null;
  }

  @Override
  public Class<Address> getType() {
    return Address.class;
  }
}

CustomField requires that you implement two methods, initContent() and getType(). initContent() creates the actual visual representation of your field. getType() tells the field which type of data will be handled by the field. In our case it is an Address object so we return Address.class in the method.

Creating the content

Next up we create the actual button that will be visible in the person editor when the CustomField is rendered. This button should open up a new window where the user can edit the address.

@Override
protected Component initContent() {
  final Window window = new Window("Edit address");
  final Button button = new Button("Open address editor", new ClickListener() {
    public void buttonClick(ClickEvent event) {
      getUI().addWindow(window);
    }
  });
  return button;
}

This is enough to attach the field to the person editor, but the window will be empty and it won’t modify the data in any way.

Creating the editable fields

The address object contains four strings - street, zip, city and country. For the three latter a TextField is good for editing, but the street address can contain multiple row so a TextArea is better here. All the fields have to be put into a layout and the layout has to be set as the content of the window. FormLayout is a good choice here to nicely align up the captions and fields of the window.

FormLayout layout = new FormLayout();
TextArea street = new TextArea("Street address:");
TextField zip = new TextField("Zip code:");
TextField city = new TextField("City:");
TextField country = new TextField("Country:");
layout.addComponent(street);
layout.addComponent(zip);
layout.addComponent(city);
layout.addComponent(country);
window.setContent(layout);

The field is now visually ready but it doesn’t contain or affect any data. You want to also modify the sizes as well to make it look a bit nicer:

window.center();
window.setWidth(null);
layout.setWidth(null);
layout.setMargin(true);

Binding the address to the field

A FieldGroup can be used to bind the data of an Address bean into the fields. We create a member variable for a FieldGroup and initialize it within the createContent() -method:

fieldGroup = new BeanFieldGroup<Address>(Address.class);
fieldGroup.bind(street, "street");
fieldGroup.bind(zip, "zip");
fieldGroup.bind(city, "city");
fieldGroup.bind(country, "country");

The FieldGroup of the person editor will call AddressPopup.setValue(person.getAddress()) when we start to edit our person. We need to override setInternalValue(Address) to get the Address object and pass it to the FieldGroup of the address editor.

@Override
protected void setInternalValue(Address address) {
  super.setInternalValue(address);
  fieldGroup.setItemDataSource(new BeanItem<Address>(address));
}

The last thing that has to be done is save the modifications made by the user back into the Address bean. This is done with a commit() call to the FieldGroup, which can be made for example when the window is closed:

window.addCloseListener(new CloseListener() {
  public void windowClose(CloseEvent e) {
    try {
      fieldGroup.commit();
    } catch (CommitException ex) {
      ex.printStackTrace();
    }
  }
});

Now you need to attach the AddressPopup custom field into the person editor through it’s FieldGroup and you have a working editor.

Complete code

package com.example.addressforms.fields;

import com.example.addressforms.data.Address;
import com.vaadin.data.fieldgroup.BeanFieldGroup;
import com.vaadin.data.fieldgroup.FieldGroup;
import com.vaadin.data.fieldgroup.FieldGroup.CommitException;
import com.vaadin.data.util.BeanItem;
import com.vaadin.ui.Button;
import com.vaadin.ui.Button.ClickEvent;
import com.vaadin.ui.Button.ClickListener;
import com.vaadin.ui.Component;
import com.vaadin.ui.CustomField;
import com.vaadin.ui.FormLayout;
import com.vaadin.ui.TextArea;
import com.vaadin.ui.TextField;
import com.vaadin.ui.Window;
import com.vaadin.ui.Window.CloseEvent;
import com.vaadin.ui.Window.CloseListener;

public class AddressPopup extends CustomField<Address> {
  private FieldGroup fieldGroup;

  @Override
  protected Component initContent() {
    FormLayout layout = new FormLayout();
    final Window window = new Window("Edit address", layout);
    TextArea street = new TextArea("Street address:");
    TextField zip = new TextField("Zip code:");
    TextField city = new TextField("City:");
    TextField country = new TextField("Country:");
    layout.addComponent(street);
    layout.addComponent(zip);
    layout.addComponent(city);
    layout.addComponent(country);

    fieldGroup = new BeanFieldGroup<Address>(Address.class);
    fieldGroup.bind(street, "street");
    fieldGroup.bind(zip, "zip");
    fieldGroup.bind(city, "city");
    fieldGroup.bind(country, "country");
    Button button = new Button("Open address editor", new ClickListener() {
      public void buttonClick(ClickEvent event) {
        getUI().addWindow(window);
      }
    });
    window.addCloseListener(new CloseListener() {
      public void windowClose(CloseEvent e) {
        try {
          fieldGroup.commit();
        } catch (CommitException ex) {
          ex.printStackTrace();
        }
      }
    });

    window.center();
    window.setWidth(null);
    layout.setWidth(null);
    layout.setMargin(true);
    return button;
  }

  @Override
  public Class<Address> getType() {
    return Address.class;
  }

  @Override
  protected void setInternalValue(Address address) {
    super.setInternalValue(address);
    fieldGroup.setItemDataSource(new BeanItem<Address>(address));
  }
}

Address editor

Address editor window