Creating Forms by Binding Fields to Items
Let us start with a data model that has an item with a couple of properties. The item could be any item type, as described earlier.
Next, you would design a form for editing the data. The FormLayout ( “FormLayout” is ideal for forms, but you could use any other layout as well.
FormLayout form = new FormLayout();
TextField nameField = new TextField("Name");
form.addComponent(nameField);
TextField ageField = new TextField("Age");
form.addComponent(ageField);
Then, we can bind the fields to the data as follows:
// Now create the binder and bind the fields
FieldGroup binder = new FieldGroup(item);
binder.bind(nameField, "name");
binder.bind(ageField, "age");
The above way of binding is not different from simply calling setPropertyDataSource() for the fields. It does, however, register the fields in the field group, which for example enables buffering or validation of the fields using the field group, as described in .
Next, we consider more practical uses for a FieldGroup.
Using a FieldFactory to Build and Bind Fields
Using the buildAndBind() methods, FieldGroup can create fields for you using a FieldGroupFieldFactory, but you still have to add them to the correct position in your layout.
The bindMemberFields() method in FieldGroup uses reflection to bind the properties of an item to field components that are member variables of a class. Hence, if you implement a form as a class with the fields stored as member variables, you can use this method to bind them super-easy.
The item properties are mapped to the members by the property ID and the name of the member variable. If you want to map a property with a different ID to a member, you can use the @PropertyId annotation for the member, with the property ID as the parameter.
// Have an item
PropertysetItem item = new PropertysetItem();
item.addItemProperty("name", new ObjectProperty<String>("Zaphod"));
item.addItemProperty("age", new ObjectProperty<Integer>(42));
// Define a form as a class that extends some layout
class MyForm extends FormLayout {
// Member that will bind to the "name" property
TextField name = new TextField("Name");
// Member that will bind to the "age" property
@PropertyId("age")
public MyForm() {
// Customize the layout a bit
setSpacing(true);
// Add the fields
addComponent(ageField);
}
}
// Create one
MyForm form = new MyForm();
// Now create a binder that can also creates the fields
// using the default field factory
FieldGroup binder = new FieldGroup(item);
binder.bindMemberFields(form);
// And the form can be used in an higher-level layout
layout.addComponent(form);
See the .
Using a CustomComponent can be better for hiding the implementation details than extending a layout. Also, the use of the FieldGroup can be encapsulated in the form class.
Consider the following as an alternative for the form implementation presented earlier:
// A form component that allows editing an item
class MyForm extends CustomComponent {
// Member that will bind to the "name" property
TextField name = new TextField("Name");
// Member that will bind to the "age" property
@PropertyId("age")
TextField ageField = new TextField("Age");
public MyForm(Item item) {
FormLayout layout = new FormLayout();
layout.addComponent(name);
layout.addComponent(ageField);
// Now use a binder to bind the members
FieldGroup binder = new FieldGroup(item);
setCompositionRoot(layout);
}
}
// And the form can be used as a component
layout.addComponent(new MyForm(item));
See the on-line example.
Buffering Forms
Just like for individual fields, as described in “Field Buffering”, a FieldGroup can handle buffering the form content so that it is written to the item data source only when commit() is called for the group. It runs validation for all fields in the group and writes their values to the item data source only if all fields pass the validation. Edits can be discarded, so that the field values are reloaded from the data source, by calling discard(). Buffering is enabled by default, but can be disabled by calling setBuffered(false) for the FieldGroup.
See the .
The BeanFieldGroup makes it easier to bind fields to a bean. It also handles binding to nested beans properties. The build a field bound to a nested bean property, identify the property with dot notation. For example, if a Person bean has a address property with an Address type, which in turn has a street property, you could build a field bound to the property with buildAndBind(“Street”, “address.street”).
The input to fields bound to a bean can be validated using the Java Bean Validation API, as described in Bean Validation. The BeanFieldGroup automatically adds a BeanValidator to every field if a bean validation implementation is included in the classpath.
Bean Validation
Using bean validation requires an implementation of the Bean Validation API, such as Hibernate Validator ( hibernate-validator-4.2.0.Final.jar or later) or Apache Bean Validation. The implementation JAR must be included in the project classpath when using the bean validation, or otherwise an internal error is thrown.
Bean validation is especially useful when persisting entity beans with the Vaadin JPAContainer, described in “Vaadin JPAContainer”.
The validation constraints are defined as annotations. For example, consider the following bean:
// Here is a bean
public class Person implements Serializable {
@NotNull
@javax.validation.constraints.Size(min=2, max=10)
String name;
@Min(1)
@Max(130)
int age;
// ... setters and getters ...
}
For a complete list of allowed constraints for different data types, please see the .
Validating a bean is done with a BeanValidator, which you initialize with the name of the bean property it should validate and add it the the editor field.
In the following example, we validate a single unbuffered field:
Person bean = new Person("Mung bean", 100);
BeanItem<Person> item = new BeanItem<Person> (bean);
// Create an editor bound to a bean field
TextField firstName = new TextField("First Name",
item.getItemProperty("name"));
// Add the bean validator
firstName.addValidator(new BeanValidator(Person.class, "name"));
firstName.setImmediate(true);
In this case, the validation is done immediately after focus leaves the field. You could do the same for the other field as well.
Bean validators are automatically created when using a BeanFieldGroup.