Blog

Making custom components for declarative use

By  
Ben Wilson
Ben Wilson
·
On Sep 19, 2017 7:00:00 AM
·

Vaadin introduced declarative syntax for initializing screen compositions in Framework 7.4. Since its release, declarative serialization and initialization have been associated for the most part with the Vaadin Designer product, where this syntax is used to describe a user interface design in a compact, procedure-free format.

Unlike other rich component frameworks like JavaFX and Microsoft’s WPF, the mapping in Vaadin of declarative attributes to component properties is both implicit and explicit, and customization is procedural. The explicit mapping mechanism is available and free to use for all developers who are building their own components.

In this article we will review how you can author your own custom Vaadin components and control the implicit and explicit mappings, and describe the challenges of attribute discoverability and how to circumnavigate these.

Why you should do this

Supporting declarative is not actually a requirement for uploading add-ons to the Vaadin Directory or for sharing custom Vaadin components within your organization. However, not supporting it (or worse, inadvertently designing the component to break on declarative use) limits its usability and makes any use in the Vaadin Designer impossible.

The constructive reason to do this of course is to reap the modelling benefits of declarative UIs, many of which can be yours regardless if you use Vaadin Designer or not. A Vaadin Designer license is not required to develop the declarative behavior of your components; testing and simulation can be achieved moreover within simple procedural patterns.

Define the class

To understand how to make your component class declarative-ready, it helps to understand the responsibilities of the class when declarative syntax is read and written. Unlike MXML and XAML, Vaadin declarative HTML is not compiled during build but parsed and interpreted at runtime.

Declarative initialization is a shared responsibility between the Vaadin framework and the individual UI components. At runtime this format is processed in the framework by classes in the com.vaadin.ui.declarative package to transform the HTML into a sequence of constructor invocations. Subsequently, responsibility shifts to the individual components to process the attributes of the HTML element with which they are associated.

At design-time, the Vaadin Designer also reads and writes the declarative format, and like with the declarative classes delegates to the component the reading and writing of attributes and their mapping to the component’s state.

The most common problems you will encounter when starting with declarative readiness are related to the accessibility of your component to participate in these collaborations. If you have created a component with only a procedural use in mind, these problems won’t necessarily appear.

Your custom component class must have public accessibility
Since the Vaadin framework and the Vaadin Designer have different packages, default accessibility is not an option.

Your class must not be in the default package
In Vaadin, declarative name resolution assumes a named package prefix separated by a dash, as in vaadin-button.

Your class must be a top-level class
Not an inner or member class

Your class must be instantiatable through a public no-arg constructor
Since the responsibilities of initialization lie with the component, the framework and Designer both need to instantiate an object which can take over the initialization of itself.

Define the attribute handling

Once you’ve got the essentials in place for Vaadin tools to create your components, you need to think about how your components will serialize and deserialize their state in a list of attributes.

Attribute handling happens in two phases during initialization. There is a specific terminology to describe which properties are handled in which phase, which we will explain in the next paragraphs. In summary, in the first phase all implicit initialization takes place; and in the second phase properties that require custom initialization are initialized following explicitly coded custom logic. The good news is component authors can control which properties fall into both categories.

To summarize with API-specific terminology, in the first phase there is the processing of supported attributes that are marked as default attributes. In a second phase, there is the processing of the rest of the attributes, which include supported, custom attributes, and unsupported attributes.

First phase: Implicit initialization
First let’s review the distinction between supported and unsupported attributes. The notion of supported attributes is codified in the static getSupportedAttributes method of com.vaadin.ui.declarative.DesignAttributeHandler. This method returns a Collection of Strings listing the attribute names that are suitable for implicit declarative initialization for a particular class.

Supported attributes involve properties that are uncomplicated to convert into a String format and can be handled implicitly by the Vaadin declarative libraries. There is nothing you as author can do to broaden the definition of what counts as “supported”. There are a number of criteria for these properties:

  1. Getter must match a setter following regular JavaBeans naming conventions
  2. Getter and setter must be public
  3. The type of the property must be one of:
    BigDecimal boolean Boolean byte Byte char Character Date double
    Double float Float integer Integer LocalDate LocalDateTime long
    Long Resource short Short ShortcutAction String TimeZone

If these three criteria are met then the Vaadin framework declarative libraries will provide implicit, default support for the serialization and no effort is required from the component author. These properties will be supported, and if the component author does not take any steps to interfere with the implicit serialization the attributes will be “supported” and “default”.

While the component author has no means to influence the behavior of the DesignAttributeHandler, they can prevent implicit handling of specific supported attributes. To do this the component must override a method from the AbstractComponent class called getCustomAttributes. Here is an example in which we prevent implicit initialization of an attribute called “placeholder”:

@Override
protected Collection<String> getCustomAttributes() {
  Collection<String> retval = super.getCustomAttributes();
  retval.add("placeholder");
  return retval;
}

Once this method has been added to a custom component, the placeholder property will be ignored during implicit serialization and initialization. In the API-specific terminology, by adding “placeholder” to the list of “custom attributes”, the attribute will be removed from the list of “default attributes”.

Second phase: Explicit initialization
Any attribute that is not “supported” or not “default” must be explicitly covered by custom attribute handling or be ignored. To implement this behavior, you override the methods readDesign and writeDesign that are defined in the Component interface. These methods are implemented in pairs, because your component should only write things that it can read again at a later date.

Under the hood, Vaadin uses jsoup to read all declarative designs, and the APIs that connect your components to their attributes represent the state as a org.jsoup.nodes.Element. Explicit attribute handling works by processing jsoup Elements returned from the declarative APIs, which are only provided to your component after all attributes have been parsed. These attributes are accessible through the Element as a LinkedHashMap that preserves the order in which they appeared in the HTML.

This means as a component author you have unusual flexibility to determine in which order you process these attributes. You could impose a fixed order, respect the order as they appear in the design, or retrieve them based on some dynamically configured ordering.

For example, consider a Table component that has a fixedColumns property - you might have validation in place to ensure that the number of fixed columns you set in your table is not larger than the total number of columns. Even though a property like fixedColumns might be a simple int that could arguably be handled implicitly, this would be a typical case for handling explicitly: You would want to ensure that the cols property was initialized before the fixedColumns property.

In the following examples we will create a custom component that contains two properties: a radiance property of type int and a weighted property of type boolean. To enable explicit initialization for these two properties we will override the inherited readDesign and writeDesign methods.

Here is a simplistic sample implementation of readDesign that initializes the properties of its object in the order in which they appeared in the design:

@Override
public void readDesign(Element design, DesignContext dc) {
  super.readDesign(design, dc);
  for (Attribute at : design.attributes()) {
    String s = at.getValue();
    switch (at.getKey()) {
      case "radiance":
        setRadiance(Integer.valueOf(s));
        break;
      case "weighted":
        setWeighted(Boolean.valueOf(s));
    }
  }
}

And for achieving the same but imposing a fixed ordering of the initialization:

@Override
public void readDesign(Element design, DesignContext dc) {
  super.readDesign(design, dc);
  if (design.hasAttr("radiance")) {
    setRadiance(Integer.valueOf(design.attr("radiance")));
  }
  if (design.hasAttr("weighted")) {
    setWeighted(Boolean.valueOf(design.attr("weighted")));
  }
}

For serializing the component state into Vaadin declarative HTML, the writeDesign method exists with a very similar signature:

@Override
public void writeDesign(Element design, DesignContext dc) {
  super.writeDesign(design, dc);
  design.attr("radiance", String.valueOf(getRadiance()));
  design.attr("weighted", String.valueOf(isWeighted()));
}

Note in the above examples we have taken shortcuts with regard to the formatting of properties for the sake of illustration and simplicity. For a safer implementation and also to have a consistent behavior for handling attributes equal to their default values (0 for int properties, false for boolean...) consider using the helper methods from the com.vaadin.ui.declarative.DesignAttributeHandler class. There you find static readAttribute and writeAttribute methods which you can use in your implementations of readDesign and writeDesign.

Testing your component

Two facets of Vaadin declarative HTML are useful for component testing.

First, the processing of Vaadin declarative statements is always geared to a root. This is a natural fit for designs of complete user interfaces which need to get set as content to a UI: UI’s setContent method expects a tree root as argument, and the Design.read method returns the root of a tree as a Component. But there is no limitation on the number of objects that can be contained in the tree, and for the Design.read method there is no requirement that the root Component actually inherit from AbstractComponentContainer or contain any children.

Second, Vaadin’s declarative syntax is not XML but HTML, and namespace resolution of package names follows custom logic. This means a valid declarative design (especially for items in the vaadin default namespace) can be tiny as several properties can be initialized in a single string.

Taking these two together, it’s very easy to do spot instantiations of visual components inside the procedural code of the view. For example:

TextField name = (TextField)Design.read(
  new java.io.StringBufferInputStream(
    "<vaadin-text-field caption='name' maxlength='8' " +
    "placeholder='enter name' />"));

You can take advantage of the easy integration of Vaadin declarative HTML inside your code to help you test your components. In the example of our component, one unit test could look as follows:

Steamer steamerA = new Steamer();
steamerA.setWeighted(true);
steamerA.setRadiance(3375);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
Design.write(steamerA, bos);
String steamerDeclara = bos.toString("UTF-8");
ByteArrayInputStream bis = 
    new ByteArrayInputStream(steamerDeclara.getBytes("UTF-8"));
Steamer steamerB = (Steamer)Design.read(bis);
Assert.assertEquals(steamerA,  steamerB);

Programming discoverability

At the moment, discovering which attributes your component supports using reflection and introspection is limited to what can be found by the getSupportedAttributes and getCustomAttributes methods discussed in the sections above. This severely limits your options for variable length properties like collections or arrays; or properties of custom reference types.

To support a variable length property in a discoverable attribute, you are limited to custom encoding of the value in Strings. String is the only supported type that is inherently variable length.

For example, consider your component has a property of type int[] called yearsActive. Even if you had a getYearsActive and setYearsActive method, years-active would never be a supported attribute due to its underlying type. You could work around that by implementing an additional JavaBeans style get/set pair representing a new property of type String that performed conversion to a private field of type int[]:

private int[] yearsActive;
public int[] getYearsActive() {
  return this.yearsActive;
}
public void setYearsActive(int[] arg) {
  this.yearsActive = arg;
}
public String getYearsActiveAsString() {
  String retval = "";
  retval = Arrays.toString(yearsActive);
  return retval;
}
public void setYearsActiveAsString(String arg) {
  yearsActive = 
    Arrays.stream(arg.substring(1, arg.length()-1).split(","))
      .map(String::trim).mapToInt(Integer::parseInt).toArray();
}

With just these two additional methods, a years-active-as-string attribute would become available and used implicitly during serialization and initialization.

The approach for custom reference type properties would require a similar workaround. You could have a String conversion getter and setter, or you could implement new property patterns for each relevant primitive field in the object.

.
Ben Wilson
Ben Wilson
Ben joined Vaadin in 2016 after specializing many years in the automated modernization of large enterprise applications. Ben works in the Berlin office and is always looking for ways to salvage parts of old software to construct new, cutting-edge applications.
Other posts by Ben Wilson