diff options
Diffstat (limited to 'documentation/datamodel/datamodel-itembinding.asciidoc')
-rw-r--r-- | documentation/datamodel/datamodel-itembinding.asciidoc | 377 |
1 files changed, 377 insertions, 0 deletions
diff --git a/documentation/datamodel/datamodel-itembinding.asciidoc b/documentation/datamodel/datamodel-itembinding.asciidoc new file mode 100644 index 0000000000..fd21b72267 --- /dev/null +++ b/documentation/datamodel/datamodel-itembinding.asciidoc @@ -0,0 +1,377 @@ +--- +title: Creating Forms by Binding Fields to Items +order: 4 +layout: page +--- + +[[datamodel.itembinding]] += Creating Forms by Binding Fields to Items + +Most applications in existence have forms of some sort. Forms contain fields, +which you want to bind to a data source, an item in the Vaadin data model. +[classname]#FieldGroup# provides an easy way to bind fields to the properties of +an item. You can use it by first creating a layout with some fields, and then +call it to bind the fields to the data source. You can also let the +[classname]#FieldGroup# create the fields using a field factory. It can also +handle commits. Notice that [classname]#FieldGroup# is not a user interface +component, so you can not add it to a layout. + +[[datamodel.itembinding.simple]] +== Simple Binding + +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. + + +---- +// Have an item +PropertysetItem item = new PropertysetItem(); +item.addItemProperty("name", new ObjectProperty<String>("Zaphod")); +item.addItemProperty("age", new ObjectProperty<Integer>(42)); +---- + +Next, you would design a form for editing the data. The [classname]#FormLayout# +( +<<dummy/../../../framework/layout/layout-formlayout#layout.formlayout,"FormLayout">> +is ideal for forms, but you could use any other layout as well. + + +---- +// Have some layout and create the fields +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 +[methodname]#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 +<<datamodel.itembinding.buffering>>. + +Next, we consider more practical uses for a [classname]#FieldGroup#. + + +[[datamodel.itembinding.fieldfactory]] +== Using a [interfacename]#FieldFactory# to Build and Bind Fields + +Using the [methodname]#buildAndBind()# methods, [classname]#FieldGroup# can +create fields for you using a [interfacename]#FieldGroupFieldFactory#, but you +still have to add them to the correct position in your layout. + + +---- +// Have some layout +FormLayout form = new FormLayout(); + +// Now create a binder that can also create the fields +// using the default field factory +FieldGroup binder = new FieldGroup(item); +form.addComponent(binder.buildAndBind("Name", "name")); +form.addComponent(binder.buildAndBind("Age", "age")); +---- + + +[[datamodel.itembinding.formclass]] +== Binding Member Fields + +The [methodname]#bindMemberFields()# method in [classname]#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 [literal]#++@PropertyId++# annotation for the member, +with the property ID as the parameter. + +For example: + + +---- +// 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") + TextField ageField = new TextField("Age"); + + public MyForm() { + // Customize the layout a bit + setSpacing(true); + + // Add the fields + addComponent(name); + 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 http://demo.vaadin.com/book-examples-vaadin7/book#datamodel.itembinding.formclass.extended[on-line example, window="_blank"]. + +[[datamodel.itembinding.formclass.customcomponent]] +=== Encapsulating in [classname]#CustomComponent# + +Using a [classname]#CustomComponent# can be better for hiding the implementation +details than extending a layout. Also, the use of the [classname]#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); + binder.bindMemberFields(this); + + setCompositionRoot(layout); + } +} + +// And the form can be used as a component +layout.addComponent(new MyForm(item)); +---- +See the http://demo.vaadin.com/book-examples-vaadin7/book#datamodel.itembinding.formclass.customcomponent[on-line example, window="_blank"]. + + + +[[datamodel.itembinding.buffering]] +== Buffering Forms + +Just like for individual fields, as described in +<<dummy/../../../framework/components/components-fields#components.fields.buffering,"Field +Buffering">>, a [classname]#FieldGroup# can handle buffering the form content so +that it is written to the item data source only when [methodname]#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 [methodname]#discard()#. Buffering is enabled by default, but +can be disabled by calling [methodname]#setBuffered(false)# for the +[classname]#FieldGroup#. + + +---- +// Have an item of some sort +final PropertysetItem item = new PropertysetItem(); +item.addItemProperty("name", new ObjectProperty<String>("Q")); +item.addItemProperty("age", new ObjectProperty<Integer>(42)); + +// Have some layout and create the fields +Panel form = new Panel("Buffered Form"); +form.setContent(new FormLayout()); + +// Build and bind the fields using the default field factory +final FieldGroup binder = new FieldGroup(item); +form.addComponent(binder.buildAndBind("Name", "name")); +form.addComponent(binder.buildAndBind("Age", "age")); + +// Enable buffering (actually enabled by default) +binder.setBuffered(true); + +// A button to commit the buffer +form.addComponent(new Button("OK", new ClickListener() { + @Override + public void buttonClick(ClickEvent event) { + try { + binder.commit(); + Notification.show("Thanks!"); + } catch (CommitException e) { + Notification.show("You fail!"); + } + } +})); + +// A button to discard the buffer +form.addComponent(new Button("Discard", new ClickListener() { + @Override + public void buttonClick(ClickEvent event) { + binder.discard(); + Notification.show("Discarded!"); + } +})); +---- +See the http://demo.vaadin.com/book-examples-vaadin7/book#datamodel.itembinding.formclass.customcomponent[on-line example, window="_blank"]. + + +[[datamodel.itembinding.beans]] +== Binding Fields to a Bean + +The [classname]#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 +[classname]#Person# bean has a [literal]#++address++# property with an +[classname]#Address# type, which in turn has a [literal]#++street++# property, +you could build a field bound to the property with +[methodname]#buildAndBind("Street", "address.street")#. + +The input to fields bound to a bean can be validated using the Java Bean +Validation API, as described in <<datamodel.itembinding.beanvalidation>>. The +[classname]#BeanFieldGroup# automatically adds a [classname]#BeanValidator# to +every field if a bean validation implementation is included in the classpath. + + +[[datamodel.itembinding.beanvalidation]] +== Bean Validation + +Vaadin allows using the Java Bean Validation API 1.0 (JSR-303) for validating +input from fields bound to bean properties before the values are committed to +the bean. The validation is done based on annotations on the bean properties, +which are used for creating the actual validators automatically. See +<<dummy/../../../framework/components/components-fields#components.fields.validation,"Field +Validation">> for general information about validation. + +Using bean validation requires an implementation of the Bean Validation API, +such as Hibernate Validator ( [filename]#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 +<<dummy/../../../framework/jpacontainer/jpacontainer-overview.asciidoc#jpacontainer.overview,"Vaadin +JPAContainer">>. + +[[datamodel.itembinding.beanvalidation.annotations]] +=== Annotations + +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 link:http://docs.oracle.com/javaee/6/tutorial/doc/gircz.html[Bean Validation +API documentation]. + + +[[datamodel.itembinding.beanvalidation.validating]] +=== Validating the Beans + +Validating a bean is done with a [classname]#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); +layout.addComponent(firstName); +---- + +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 +[classname]#BeanFieldGroup#. + + +---- +// Have a bean +Person bean = new Person("Mung bean", 100); + +// Form for editing the bean +final BeanFieldGroup<Person> binder = + new BeanFieldGroup<Person>(Person.class); +binder.setItemDataSource(bean); +layout.addComponent(binder.buildAndBind("Name", "name")); +layout.addComponent(binder.buildAndBind("Age", "age")); + +// Buffer the form content +binder.setBuffered(true); +layout.addComponent(new Button("OK", new ClickListener() { + @Override + public void buttonClick(ClickEvent event) { + try { + binder.commit(); + } catch (CommitException e) { + } + } +})); +---- + + +[[datamodel.itembinding.beanvalidation.locale]] +=== Locale Setting for Bean Validation + +The validation error messages are defined in the bean validation implementation, +in a [filename]#ValidationMessages.properties# file. The message is shown in the +language specified with the locale setting for the form. The default language is +English, but for example Hibernate Validator contains translations of the +messages for a number of languages. If other languages are needed, you need to +provide a translation of the properties file. + + + + + |