Design and build a collaborative user experience for a chance to win cool prizes!
Blog

Vaadin Declarative 101

By  
Joonas Lehtinen
Joonas Lehtinen
·
On Jan 15, 2015 8:02:00 AM
·

Did you hear the big news that Vaadin 7.4 will include a declarative design language? Did you see the new Vaadin Designer in Vaadin Labs? The next question is how these should be used? Below I propose a set of naming and use conventions - “best practices” if you will.

Goals

The declarative definition of user interface in Vaadin complements programmatic construction. Both declarative and programmatic development models can be used together and one should choose the tool for each purpose depending on your use-case and personal preferences. Relatively static user interfaces like forms and dialogs are often easier and faster to build declaratively. More dynamic user interfaces that adapt to represented data can only be built programmatically. Often combining these two gives the best of both worlds.

Concepts

A declaratively defined composition is called a design. Each design is based on a layout component that we call design root. Inside the root there are components and possibly more layouts.

Design is defined as a subset of HTML5 that is extended with custom elements - one for each Vaadin component. These custom elements follow conventions from Web Component standards to allow supporting Web Components in the future versions of Vaadin. All components from the com.vaadin.ui package are available as HTML5 elements named <v-componentclassname>. Components from other packages can be imported by adding <meta name="package-mapping" content="my:com.addon.mypackage" /> to <head> section of the file. If the design HTML file does not import other components, one can leave out everything outside <body> to keep the files simple.

Designs are loaded to Java with the Design.read() method. Any component tree can be serialized to a design with Design.write() method.

Each design typically has a Java class for design root that defines references to the components in the design. This class must have the @DesignRoot annotation and extend the class of the root layout because it will be used to instantiate the root layout of the design. The aforementioned Design.read() method will automatically add references to child components in the design root class by matching their names to IDs given in the design by the _id attribute or by deriving the IDs from component captions.

Conventions

While one can load design files directly in the UI class, adding event handlers and data sources to the components would be cumbersome. Instead we recommend building a corresponding design root class that has Java references directly to components defined in the design. Four ways of doing this and recommended naming conventions are listed below.

Writing designs by hand

For example, let’s define a shipping form in the following file:

ShippingForm.html

<v-vertical-layout width="100%" spacing>
    <v-text-field caption="Name" width="100%" />
    <v-text-field caption="Street Address Line 1" width="100%" />
    <v-text-field caption="Street Address Line 2" width="100%" />
    <v-horizontal-layout width="100%" spacing>
        <v-text-field caption="City" :expand width="100%"/>
        <v-combo-box caption="State" visible="false" />
        <v-text-field caption="Zip" columns="5" />
        <v-combo-box caption="Country" />
    </v-horizontal-layout>
</v-vertical-layout>

Extending a Layout

To define some functionality and validators for the shipping form component, one should define the following class:

ShippingForm.java

@DesignRoot
public class ShippingForm extends VerticalLayout {

    TextField name;
    TextField streetAddressLine1;
    TextField streetAddressLine2;
    TextField city;
    TextField zip;
    ComboBox state;
    ComboBox country;

    public ShippingForm() {
        Design.read(this);
        country.setContainerDataSource(Countries.getContainer());
        country.addValueChangeListener(e -> {
            Countries.setStatesOrHide((String) country.getValue(), state);
        });
        // validators and other functionality ...
    }
}

Wrapping in CustomComponent

The problem with the approach above is that the VerticalLayout API is exposed to all users of the ShippingForm. If this is not wanted, one should extend CustomComponent to hide the unwanted API:

ShippingForm.java

public class ShippingForm extends CustomComponent {

    protected ShippingFormDesign design = new ShippingFormDesign();

    @DesignRoot
    protected static class ShippingFormDesign extends VerticalLayout {
        TextField name;
        TextField streetAddressLine1;
        TextField streetAddressLine2;
        TextField city;
        TextField zip;
        ComboBox state;
        ComboBox country;
    }

    public ShippingForm() {
        Design.read(design);
        setCompositionRoot(design);
        setWidth(design.getWidth(), design.getWidthUnits());
        design.country.setContainerDataSource(Countries.getContainer());
        design.country.addValueChangeListener(e -> {
            Countries.setStatesOrHide((String) design.country.getValue(), design.state);
        });
        // validators and other functionality ...
    }
}

Vaadin Designer

Vaadin Designer can save a lot of work when using declarative designs, because it will write most of the code seen above for you automatically. Whenever you draw a new design or update an existing one, the following two files are created for you:

  • ShippingFormDesign.html contains the design as HTML
  • ShippingFormDesign.java contains field references and can be used from Java

Both files are generated by Vaadin Designer and thus they should be edited in the Designer. ShippingFormDesign.html looks like the ShippingForm.html listed above, but will also include full HTML5 headers. The recommended convention is to add the “Design” postfix to the names to explicitly separate the design class from the ShippingForm discussed below. ShippingFormDesign.java is almost like ShippingForm.java above, but will include an @AutoGenerated annotation and omit the customizations from the constructor.

To add dynamic UI logic and other functionality to the shipping form, you need to extend the class. As when writing the files manually, there are two approaches: extending the design root layout or creating a custom component.

Extending a Layout

ShippingForm.java

public class ShippingForm extends ShippingFormDesign {

    public ShippingForm() {
        country.setContainerDataSource(Countries.getContainer());
        country.addValueChangeListener(e -> {
            Countries.setStatesOrHide((String) country.getValue(), state);
        });
        // validators and other functionality ...
    }
}

Wrapping in CustomComponent

ShippingForm.java

public class ShippingForm extends CustomComponent {

    protected ShippingFormDesign design = new ShippingFormDesign();

    public ShippingForm() {
        setWidth(design.getWidth(), design.getWidthUnits());
        setCompositionRoot(design);
        design.country.setContainerDataSource(Countries.getContainer());
        design.country.addValueChangeListener(e -> {
            Countries.setStatesOrHide((String) design.country.getValue(), design.state);
        });
        // validators and other functionality ...
    }
}
Joonas Lehtinen
Joonas Lehtinen
Joonas is the CEO and co-founder of Vaadin. He has been working with web app development tools and technologies for over 20 years and speaks frequently in Java conferences around the world. Joonas lives is San Jose, CA, with his wife and 10 year old son. You can follow him on Twitter – @joonaslehtinen
Other posts by Joonas Lehtinen