Forms and Binder
A form shows an object's fields and writes what the user entered back into it. Doing this by hand is tedious: every field needs setValue when the form opens and getValue on save. Binder is a Vaadin helper that links a form field to an object property once, and then moves values both ways and validates them by itself.
The new post form
// src/main/java/com/example/blog/views/PostFormView.java
package com.example.blog.views;
import com.example.blog.Post;
import com.example.blog.PostService;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.checkbox.Checkbox;
import com.vaadin.flow.component.datepicker.DatePicker;
import com.vaadin.flow.component.formlayout.FormLayout;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.TextArea;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.binder.Binder;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import java.time.LocalDate;
@Route("posts")
@PageTitle("New post")
public class PostFormView extends VerticalLayout {
private final TextField title = new TextField("Title");
private final TextField author = new TextField("Author");
private final DatePicker publishedOn = new DatePicker("Date");
private final Checkbox published = new Checkbox("Published");
private final TextArea content = new TextArea("Text");
private final Binder<Post> binder = new Binder<>(Post.class);
private Post post = new Post();
public PostFormView(PostService service) {
binder.forField(title).asRequired("Enter a title").bind(Post::getTitle, Post::setTitle);
binder.forField(author).asRequired("Enter the author").bind(Post::getAuthor, Post::setAuthor);
binder.forField(publishedOn).bind(Post::getPublishedOn, Post::setPublishedOn);
binder.forField(published).bind(Post::isPublished, Post::setPublished);
binder.forField(content).bind(Post::getContent, Post::setContent);
post.setPublishedOn(LocalDate.now());
binder.readBean(post);
Button save = new Button("Save", event -> {
if (binder.writeBeanIfValid(post)) {
service.save(post);
Notification.show("Post saved");
}
});
FormLayout form = new FormLayout(title, author, publishedOn, published, content);
form.setColspan(content, 2);
add(form, save);
}
}Open http://localhost:8080/posts: a two-column form, with today's date already in the date field. Click "Save" with an empty title — the field turns red with "Enter a title", and nothing is saved. Fill in the title and author — "Post saved" appears, and the grid on the main page now has four rows.
How it works
| Method | What it does |
|---|---|
forField(field) | Starts describing the binding for a field |
asRequired("...") | The field is required; the text is the error message |
bind(getter, setter) | Where to read the value from and where to write it |
readBean(post) | Fills the fields with values from the object |
writeBeanIfValid(post) | Validates the fields and, if all is fine, writes them to the object |
While the user types, the post object does not change. Values reach it only on writeBeanIfValid, and only if all checks pass.
Automatic binding
If the form fields have the same names as the object's properties, you do not need to bind them one by one:
// src/main/java/com/example/blog/views/PostFormView.java (part of the constructor)
binder.bindInstanceFields(this);Binder finds the fields title, author, publishedOn, published and content and binds them to the Post properties with the same names. Required fields are then set by annotations on the entity — the topic of the next lesson.
readBean or setBean
| Approach | When the object changes |
|---|---|
readBean + writeBeanIfValid | Only on the "Save" button |
setBean(post) | Right away on every field change |
setBean suits instant settings, but for a form with a "Cancel" button readBean is better: cancelled edits never reach the object.
Common mistake
The fields in the class are named differently from the entity properties, and bindInstanceFields is called:
// src/main/java/com/example/blog/views/PostFormView.java (wrong)
private final TextField name = new TextField("Title");The name field silently stays unbound. If no field matches at all, the view fails:
java.lang.IllegalStateException: There are no instance fields found for automatic bindingName fields after the properties (title) or bind them by hand with forField(...).bind("title").