How-To

Customizing existing tables

This section assumes that you are familiar with table and row PMOs.

This section discusses a common issue with reusing PMO code that models tables and their rows. This particular issue arises when a specific table PMO class is extended, e.g, to show new columns. Let’s have a look at a concrete example:

CustomRowExample.java
class PartnerTablePmo extends SimpleTablePmo<Partner, PartnerRowPmo> {

    protected PartnerTablePmo(Supplier<List<? extends Partner>> modelObjectsSupplier) {
        super(modelObjectsSupplier);
    }

    @Override
    protected PartnerRowPmo createRow(Partner partner) {
        return new PartnerRowPmo(partner);
    }
}

class PartnerRowPmo {
    private final Partner partner;

    public PartnerRowPmo(Partner partner) {
        this.partner = partner;
    }

    @UITextField(position = 10, label = "Name")
    public String getName() {
        return partner.getName();
    }

    protected Partner getPartner() {
        return partner;
    }
}

As can be seen, there are two classes. PartnerRowPmo models a row that has one column showing a Partners name. It is used within PartnerTablePmo to create a row for each instance of Partner.

Now, let’s assume that we need another table that shows the same information plus the date of birth. To implement this, we could introduce a new tablePmo and rowPmo class that would look mostly the same as the ones we had before with the row having an additional @UIDateField to visualise the date of birth. While this would leave us with quite a bit of redundant code, we could use inheritance instead.

First, we create a subclass of PartnerRowPmo and add a new getter method for the date of birth together with an @UIDateField annotation.

CustomRowExample.java
class CustomPartnerRowPmo extends PartnerRowPmo {

    public CustomPartnerRowPmo(Partner partner) {
        super(partner);
    }

    @UIDateField(position = 20, label = "Date of Birth")
    public LocalDate getDateOfBirth() {
        return getPartner().getDateOfBirth();
    }
}

Second, we create a subclass of PartnerTablePmo and overwrite the two methods createRow and getItemPmoClass.

CustomRowExample.java
class CustomPartnerTablePmo extends PartnerTablePmo {

    protected CustomPartnerTablePmo(Supplier<List<? extends Partner>> modelObjectsSupplier) {
        super(modelObjectsSupplier);
    }

    @Override
    protected PartnerRowPmo createRow(Partner partner) {
        return new CustomPartnerRowPmo(partner);
    }

    // Attention:
    // It does not suffice to solely overwrite createRow.
    // getItemPmoClass must be altered to return the new row type.
    @Override
    public Class<? extends PartnerRowPmo> getItemPmoClass() {
        return CustomPartnerRowPmo.class;
    }
}

createRow returns instances of our new CustomPartnerRowPmo class, while getItemPmoClass tells linkki the exact type of those instances. Usually, developers forget to overwrite getItemPmoClass, which has the effect that the new table (here CustomPartnerTablePmo) has the same rows as the table it extends from (here PartnerTablePmo).

You might ask yourself at this point, why you do not have to overwrite getItemPmoClass in PartnerTablePmo. The default implementation for this method is able to determine the generic type of the rowPmo class at runtime as long as it isn’t further subclassed.

This behavior is not related to type erasure. linkki can indeed determine the row type at runtime because subclassing SimpleTablePmo means to bind specific types to each generic. These types can then be looked up via reflection.

Inheriting from PartnerRowPmo means to also inherit the generic binding and linkki cannot determine the new rowPmo type on its own. Hence, we have to help out and return the correct type ourselves.