Using Model Encoders with a PolymerTemplate Model

Sometimes you may want to use ready made beans in your model which you can’t control (i.e. they are given to you as binary class files). But their structure may be inappropriate for your template model used in a user interface representation.

It usually happens if you use data base backend with JPA entities. Such entities usually have unique identifier with Long type. Long type is not supported by Flow at all (it can’t be mapped correctly to any client type). So you have to exclude any property with this type (see @Exclude in Using Beans with a PolymerTemplate Model).

But what if you need this property on the client side for some reason (e.g. you want this identifier of a bean to be able to reference it)? In this case you can use @Encode annotation. You can just encode a Long value to a String value and it will be sent to the client-side as a string. Then once the client side sends this value back it will be decoded back to a Long.

Here is the Person JPA entity class with the model class :

@Entity
public class Person implements Serializable {
    @Id
    @GeneratedValue
    private Long id;

    public Long getId() {
        return id;
    }

}

public interface MyModel extends TemplateModel {
   @Encode(value=LongToStringEncoder.class, path="id")
   void setPerson(Person person);
   Person getPerson();
}

And here is its encoder:

public class LongToStringEncoder implements ModelEncoder<Long, String> {

    @Override
    public String encode(Long modelValue) {
        return Optional.ofNullable(modelValue).map(Object::toString)
                .orElse(null);
    }

    @Override
    public Long decode(String presentationValue) {
        return Optional.ofNullable(presentationValue).map(Long::valueOf)
                .orElse(null);
    }

}

Now you can access id property of the Person bean in your code on the client side in the usual way but it will have the String type.

Note
The annotation parameter path="id" is used to address the id sub-property of the person property. By default the path value is "" which means that an encoder is applied to the property itself.

You can use any type to encode to any supported type. Another usecase for encoders may be splitting one property value to several sub-properties to use them in different UI controls instead of one. E.g. we may want to have 3 input fields for a person birthday instead of one: one for the day, month and year. So you have a Date property in your model:

public interface MyModel extends TemplateModel {

    Date getBirthDate();

    @Encode(DateToDateBeanEncoder.class)
    void setBirthDate(Date birthDate);
}

Then you may define a bean DateBean:

public class DateBean implements Serializable {

    private String day;
    private String month;
    private String year;

    public String getDay() {
        return day;
    }

    public void setDay(String day) {
        this.day = day;
    }

    public String getMonth() {
        return month;
    }

    public void setMonth(String month) {
        this.month = month;
    }

    public String getYear() {
        return year;
    }

    public void setYear(String year) {
        this.year = year;
    }

}

and the encoder class:

public class DateToDateBeanEncoder implements ModelEncoder<Date, DateBean> {

    @Override
    public DateBean encode(Date modelValue) {
        if (modelValue == null) {
            return null;
        }
        DateBean bean = new DateBean();
        Calendar calendar = GregorianCalendar.getInstance();
        calendar.setTime(modelValue);
        bean.setDay(Integer.toString(calendar.get(Calendar.DAY_OF_MONTH)));
        bean.setMonth(Integer.toString(calendar.get(Calendar.MONTH) + 1));
        bean.setYear(Integer.toString(calendar.get(Calendar.YEAR)));
        return bean;
    }

    @Override
    public Date decode(DateBean presentationValue) {
        if (presentationValue == null) {
            return null;
        }
        int year = Integer.parseInt(presentationValue.getYear());
        int day = Integer.parseInt(presentationValue.getDay());
        int month = Integer.parseInt(presentationValue.getMonth()) - 1;
        Calendar calendar = GregorianCalendar.getInstance();
        calendar.set(year, month, day);
        return calendar.getTime();
    }

}

Now you can use the following HTML template file for your component (here is only the template snippet):

<template>
    <div style="width: 200px;">
        <label>Birth date:</label>
        <label for="day">Enter your birthday:</label><paper-input id="day" value="{{birthDate.day}}"></paper-input>
        <label for="month">Enter the month of your birthday:</label><paper-input id="month" value="{{birthDate.month}}"></paper-input>
        <label for="year">Enter the year of your birthday:</label><paper-input id="year" value="{{birthDate.year}}"></paper-input>
        <button on-click="commit" id="commit">Commit</button>
    </div>
</template>

So here one Date property is encoded to 3 sub-properties: the day, month and year. Each of them has its own editor but on the server side it’s still the same one property birthDate.

Note
Please note that you still need use your original property name birthDate in this example to access to sub-properties. So those 3 sub-properties requires prefix which is the original property name and its name is still the same birthDate (and not a dateBean e.g.).