summaryrefslogtreecommitdiffstats
path: root/documentation/datamodel/datamodel-itembinding.asciidoc
blob: fd21b72267885cfdb623e4537d458f6ab86191f2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
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.