Architecture

Binding to the Domain Model (Data on Multiple Layers)

As implicated in Data Binding with PMOs a linkki application usually consists of multiple layers.

layers

This image intends to highlight where the presentation model layer is situated with respect to UI and domain model.

The rightmost layer is some kind of a persistence layer, for example a database. To the left are the domain model layer, the presentation model layer and finally the UI. The UI data binding works between the UI and the presentation model. It is responsible for keeping them in sync.

Very often the presentation model and the domain model have similar structure and properties. In that case the "model binding" can be used. "Model Binding", short-hand for Domain Model Binding, is a linkki feature for reducing delegate code in PMO classes. It is aimed at the "simple" case of UI binding, in which properties from the domain model are directly presented in the UI, meaning that no special formatting or conversion needs to take place before the value can be shown.

To use this feature, the UI needs to be created via @UISection, and the instance field or the getter method for the model object in the PMO needs to be marked with the @ModelObject annotation:

    @ModelObject
    private final Person person;
    @ModelObject
    public Person getPerson() {
        return person;
    }

For example, the firstname property from the model can be used without conversion in the UI:

    @UITextField(position = 10, label = "First Name")
    public String getFirstName() {
        return getPerson().getFirstname();
    }

In this case the getter method ensures a direct delegation to the model attribute. Therefore, the same behavior can be achieved using the model binding:

    @UITextField(position = 10, label = "First Name", modelAttribute = "firstname")
    public void firstName() {
    }

Model binding avoids unnecessary code dealing with delegation and is thus a "shortcut".

ModelBinding
Figure 1. Model Binding

Names of Model Attributes

To use the model binding feature, the property modelAttribute must be set in the UI Annotation. The data type of the property is String. The value of modelAttribute defines the name of the corresponding property in the domain model class. If no matching property exists in the domain model class, an exception is thrown during generation of the UI.

If modelAttribute is given, linkki ignores the return value and the body of the method in the PMO. Therefore the method body can remain empty and the return type be void. The data binding will skip the PMO method and call the corresponding method in the domain model directly.

In the example above the method in the PersonSectionPmo is called firstName(). In the domain model, however, it is called firstname or getFirstname(). It is possible to name properties in PMO and domain model differently.

linkki searches the model for all required methods. If the setter method exists in the model but needs to be ignored in the UI, the shortcut cannot be used. Model attributes can also be set on annotated getter methods. In that case linkki searches for all methods in the model that it can’t find in the PMO.

If the modelAttribute is set in the annotation of a non getter method, the whole method name is treated as a PMO property. This name is then used to bind UI Properties such as enabled state, visibility or available values.

Multiple Model Objects

Model binding can also be used if a PMO has multiple model objects. In this case, the property modelObject can be set in the UI annotation. The value of modelObject defines which model object of the PMO should be used for model binding. As identifier of model objects the name given in the @ModelObject annotation is used.

@UISection
public class ContractSectionPmo {

    private final Contract contract;


    public ContractSectionPmo(Contract contract) {
        this.contract = contract;
    }


    @ModelObject
    public Person getPolicyHolder() {
        return contract.getPolicyHolder();
    }

    @ModelObject(name = "IP")
    public Person getInsuredPerson() {
        return contract.getInsuredPerson();
    }


    @UITextField(position = 10, label = "First Name PH", modelAttribute = "firstname")
    public void firstNamePolicyHolder() {
    }

    @UITextField(position = 20, label = "First Name IP", modelAttribute = "firstname", modelObject = "IP")
    public void firstNameInsuredPerson() {
    }

}

In the example above the annotation of firstNameInsuredPerson() defines that the property firstname on model object IP shall be called. To determine the value for the presentation on the UI, the data binding proceeds as follows:

  • Call the method getInsuredPerson() on the PMO, as that method is annotated with @ModelObject("IP")

  • Save the returned Person object

  • Call the getter method for firstname on the found Person object

If the annotation contains no property modelObject, the method annotated with @ModelObject (without name) is called. Correspondingly the model object for firstNamePolicyHolder() is determined via getPolicyHolder().

For situations where a method annotated with @ModelObject returns null, further getter method may have to be implemented. These methods must delegate to the existing model object or return default values if the model object is unavailable. The setter methods do not have to be implemented explicitly. The fields are treated as read only as long as no model object exists. As soon as one is available, the model object’s setter method is called automatically.