This tutorial teaches you the basics of Vaadin 7 with a simple one class addressbook application. It is designed to be a fast read, instead of trying to follow good design practices.

Run the application

Running locally

Get the source from Github and run it:

  • git clone git://github.com/vaadin/addressbook.git
  • cd addressbook
  • mvn jetty:run

Learn more

Read the Book of Vaadin - it is a comprehensive, up-to-date and free guide on everything about Vaadin.

package com.vaadin.tutorial.addressbook; import com.vaadin.annotations.Title; import com.vaadin.data.Container.Filter; import com.vaadin.data.Item; import com.vaadin.data.Property; import com.vaadin.data.Property.ValueChangeEvent; import com.vaadin.data.fieldgroup.FieldGroup; import com.vaadin.data.util.IndexedContainer; import com.vaadin.event.FieldEvents.TextChangeEvent; import com.vaadin.event.FieldEvents.TextChangeListener; import com.vaadin.server.VaadinRequest; import com.vaadin.ui.AbstractTextField.TextChangeEventMode; import com.vaadin.ui.Button; import com.vaadin.ui.Button.ClickEvent; import com.vaadin.ui.Button.ClickListener; import com.vaadin.ui.FormLayout; import com.vaadin.ui.HorizontalLayout; import com.vaadin.ui.HorizontalSplitPanel; import com.vaadin.ui.Table; import com.vaadin.ui.TextField; import com.vaadin.ui.UI; import com.vaadin.ui.VerticalLayout;
User Interface

When you browse to your app a web page showing your UI is automatically generated. You may deploy your UI as servlet or portlet.

@Title("Addressbook") public class AddressbookUI extends UI { private Table contactList = new Table(); private TextField searchField = new TextField(); private Button addNewContactButton = new Button("New"); private Button removeContactButton = new Button("Remove this contact"); private FormLayout editorLayout = new FormLayout(); private FieldGroup editorFields = new FieldGroup();
Data source
User interface to an external data source. Here we use an in-memory list, but there are many more practical implementations.
IndexedContainer contactContainer = createDummyDatasource(); private static final String FNAME = "First Name"; private static final String LNAME = "Last Name"; private static final String COMPANY = "Company"; private static final String[] fieldNames = new String[] { FNAME, LNAME, COMPANY, "Mobile Phone", "Work Phone", "Home Phone", "Work Email", "Home Email", "Street", "City", "Zip", "State", "Country" };
After UI class is created, init() is executed. You should build and wire up your user interface here.
protected void init(VaadinRequest request) { initLayout(); initContactList(); initEditor(); initSearch(); initAddRemoveButtons(); }
Build layout
In this example layouts are programmed in Java. You may choose use a visual editor, CSS or HTML templates for layout instead.

Root of the user interface component tree is set. Then other components are added in the tree.

The contents of the leftLayout is configured to use all available space. Table inside the layout is expanded to fill the space left after the components in the bottomLeftLayout have been set to their natural sizes.

Move your mouse over the lines in this method to see the components.

private void initLayout() {
HorizontalSplitPanel splitPanel = new HorizontalSplitPanel();
VerticalLayout leftLayout = new VerticalLayout();
HorizontalLayout bottomLeftLayout = new HorizontalLayout();
leftLayout.setExpandRatio(contactList, 1);
bottomLeftLayout.setExpandRatio(searchField, 1);
editorLayout.setVisible(false); }
Dynamic forms

User interface can be created dynamically to reflect the underlying data. We use a FieldGroup to bind components to a data source.

We choose to write changes through to data source instead of buffering and committing explicitly.

private void initEditor() { for (String fieldName : fieldNames) { TextField field = new TextField(fieldName); editorLayout.addComponent(field); field.setWidth("100%"); editorFields.bind(field, fieldName); } editorLayout.addComponent(removeContactButton); editorFields.setBuffered(false); } private void initSearch() {
Event handling

Granularity for sending events over the wire can be controlled. By default simple changes like writing a text in TextField are sent to server with the next Ajax call. You can configure your component to send the changes to server immediately after focus leaves the field. Here we choose to send the text over the wire as soon as user stops writing for short a moment.

When the event happens, we handle it in the anonymous inner class. You may instead choose to use a separate named controller class. In the end, the preferred application architecture is up to you.

searchField.setInputPrompt("Search contacts"); searchField.setTextChangeEventMode(TextChangeEventMode.LAZY); searchField.addTextChangeListener(new TextChangeListener() { public void textChange(final TextChangeEvent event) { contactContainer.removeAllContainerFilters(); contactContainer.addContainerFilter(new ContactFilter(event .getText())); } }); } private class ContactFilter implements Filter { private String needle; public ContactFilter(String needle) { this.needle = needle.toLowerCase(); } public boolean passesFilter(Object itemId, Item item) { String haystack = ("" + item.getItemProperty(FNAME).getValue() + item.getItemProperty(LNAME).getValue() + item .getItemProperty(COMPANY).getValue()).toLowerCase(); return haystack.contains(needle); } public boolean appliesToProperty(Object id) { return true; } } private void initAddRemoveButtons() { addNewContactButton.addClickListener(new ClickListener() { public void buttonClick(ClickEvent event) {
Data model access

Rows in the Container data model are called Items. Here we add a new row to the beginning of the list.

Each Item has a set of Properties that hold values. Here we set the value for two of the properties.

contactContainer.removeAllContainerFilters(); Object contactId = contactContainer.addItemAt(0); contactList.getContainerProperty(contactId, FNAME).setValue( "New"); contactList.getContainerProperty(contactId, LNAME).setValue( "Contact"); contactList.select(contactId); } }); removeContactButton.addClickListener(new ClickListener() { public void buttonClick(ClickEvent event) { Object contactId = contactList.getValue(); contactList.removeItem(contactId); } }); } private void initContactList() { contactList.setContainerDataSource(contactContainer); contactList.setVisibleColumns(new String[] { FNAME, LNAME, COMPANY }); contactList.setSelectable(true); contactList.setImmediate(true); contactList.addValueChangeListener(new Property.ValueChangeListener() { public void valueChange(ValueChangeEvent event) { Object contactId = contactList.getValue();
Binding data
When a contact is selected from the list, we want to show that in our editor on the right. This is nicely done by the FieldGroup that binds all the fields to the corresponding Properties in our contact at once.
if (contactId != null) editorFields.setItemDataSource(contactList .getItem(contactId)); editorLayout.setVisible(contactId != null); } }); }
Dummy data

Generate some in-memory example data to play with. In a real application we could be using SQLContainer, JPAContainer or some other to persist the data.

Create dummy data by randomly combining first and last names

private static IndexedContainer createDummyDatasource() { IndexedContainer ic = new IndexedContainer(); for (String p : fieldNames) { ic.addContainerProperty(p, String.class, ""); } String[] fnames = { "Peter", "Alice", "Joshua", "Mike", "Olivia", "Nina", "Alex", "Rita", "Dan", "Umberto", "Henrik", "Rene", "Lisa", "Marge" }; String[] lnames = { "Smith", "Gordon", "Simpson", "Brown", "Clavel", "Simons", "Verne", "Scott", "Allison", "Gates", "Rowling", "Barks", "Ross", "Schneider", "Tate" }; for (int i = 0; i < 1000; i++) { Object id = ic.addItem(); ic.getContainerProperty(id, FNAME).setValue( fnames[(int) (fnames.length * Math.random())]); ic.getContainerProperty(id, LNAME).setValue( lnames[(int) (lnames.length * Math.random())]); } return ic; } }