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 HTMLShippingFormDesign.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 ...
}
}