Objective:
Bind a specific POJO to a freemarker template and render the object as an html element.
Steps:
First, your project should include freemarker:
<dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.26-incubating</version> </dependency>
Next, you need a proper pojo containing multiple fields. Here's the pojo I'm trying to customize:
import java.io.Serializable; import javax.annotation.Generated; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.juneau.annotation.BeanProperty; @JsonInclude(JsonInclude.Include.NON_NULL) @Generated("org.jsonschema2pojo") @JsonPropertyOrder({ "addr1", "addr2", "country", "city", "state", "zip" }) public class Adr implements Serializable { @JsonProperty("addr1") @BeanProperty("addr1") private String addr1; @JsonProperty("addr2") @BeanProperty("addr2") private String addr2; @JsonProperty("country") @BeanProperty("country") private String country; @JsonProperty("city") @BeanProperty("city") private String city; @JsonProperty("state") @BeanProperty("state") private String state; @JsonProperty("zip") @BeanProperty("zip") private String zip; /** * * @return * The addr1 */ @JsonProperty("addr1") @BeanProperty("addr1") public String getAddr1() { return addr1; } /** * * @param addr1 * The addr1 */ @JsonProperty("addr1") @BeanProperty("addr1") public void setAddr1(String addr1) { this.addr1 = addr1; } public Adr withAddr1(String addr1) { this.addr1 = addr1; return this; } /** * * @return * The addr2 */ @JsonProperty("addr2") @BeanProperty("addr2") public String getAddr2() { return addr2; } /** * * @param addr2 * The addr2 */ @JsonProperty("addr2") @BeanProperty("addr2") public void setAddr2(String addr2) { this.addr2 = addr2; } public Adr withAddr2(String addr2) { this.addr2 = addr2; return this; } /** * * @return * The country */ @JsonProperty("country") @BeanProperty("country") public String getCountry() { return country; } /** * * @param country * The country */ @JsonProperty("country") @BeanProperty("country") public void setCountry(String country) { this.country = country; } public Adr withCountry(String country) { this.country = country; return this; } /** * * @return * The city */ @JsonProperty("city") @BeanProperty("city") public String getCity() { return city; } /** * * @param city * The city */ @JsonProperty("city") @BeanProperty("city") public void setCity(String city) { this.city = city; } public Adr withCity(String city) { this.city = city; return this; } /** * * @return * The state */ @JsonProperty("state") @BeanProperty("state") public String getState() { return state; } /** * * @param state * The state */ @JsonProperty("state") @BeanProperty("state") public void setState(String state) { this.state = state; } public Adr withState(String state) { this.state = state; return this; } /** * * @return * The zip */ @JsonProperty("zip") @BeanProperty("zip") public String getZip() { return zip; } /** * * @param zip * The zip */ @JsonProperty("zip") @BeanProperty("zip") public void setZip(String zip) { this.zip = zip; } public Adr withZip(String zip) { this.zip = zip; return this; } @Override public String toString() { return ToStringBuilder.reflectionToString(this); } @Override public int hashCode() { return new HashCodeBuilder().append(addr1).append(addr2).append(country).append(city).append(state).append(zip).toHashCode(); } @Override public boolean equals(Object other) { if (other == this) { return true; } if ((other instanceof Adr) == false) { return false; } Adr rhs = ((Adr) other); return new EqualsBuilder().append(addr1, rhs.addr1).append(addr2, rhs.addr2).append(country, rhs.country).append(city, rhs.city).append(state, rhs.state).append(zip, rhs.zip).isEquals(); } }
Next, create a freemarker template to translate an instance of this bean type into a specific HTML element.
Here's an example that renders the address class as a DIV, using a conditional on the addr2 field, and combining city state and zip into a single row.
<div> <table> <tr> <td> ${addr1} </td> </tr> <#if addr2?has_content> <tr> <td> ${addr2} </td> </tr> </#if> <tr> <td> ${city}, ${state} ${zip} </td> </tr> </table> </div>
Next, you need to create an HtmlRender class for this type of bean. For example:
import freemarker.template.Configuration; import freemarker.template.Template; import freemarker.template.TemplateExceptionHandler; import org.apache.juneau.dto.html5.Div; import org.apache.juneau.dto.html5.Form; import org.apache.juneau.html.HtmlParser; import org.apache.juneau.html.HtmlRender; import org.apache.juneau.serializer.SerializerSession; import Adr; import java.io.StringWriter; import java.nio.charset.Charset; import java.util.Locale; import static org.apache.juneau.dto.html5.HtmlBuilder.div; import static org.apache.juneau.dto.html5.HtmlBuilder.h2; /** * Created by sblackmon on 7/17/17. */ public class MemberAddressRender extends HtmlRender<Adr> { static Configuration cfg = new Configuration(Configuration.VERSION_2_3_26); static { cfg.setClassLoaderForTemplateLoading(MemberAddressRender.class.getClassLoader()); cfg.setEncoding(Locale.US, Charset.defaultCharset().name()); cfg.setTemplateExceptionHandler(TemplateExceptionHandler.HTML_DEBUG_HANDLER); } @Override public Div getContent(SerializerSession session, Adr value) { StringWriter stringWriter = new StringWriter(); try { Template myTemplate = cfg.getTemplate("MemberAddressRender.div.ftl"); myTemplate.process(value, stringWriter); } catch( Exception ex ) { ex.printStackTrace(); } String divString = stringWriter.toString(); Div div = div(); try { div = HtmlParser.DEFAULT.parse(divString, Div.class); } catch( Exception ex ) { ex.printStackTrace(); } return div; } }
Finally, you need to bind the custom Render class onto the class(es) which have the class you are customizing as a child.
import org.apache.juneau.html.annotation.Html; import Adr; import Member; /** * Created by sblackmon on 7/17/17. */ public class MemberHtml extends Member { @Override @Html(link = "servlet:/{username}") public String getUsername() { return super.getUsername(); } @Override @Html(render=MemberAddressRender.class) public Adr getAdr() { return super.getAdr(); } @Override @Html(render=MemberImageRender.class) public String getPicture() { return super.getPicture(); } }
In case you are interested, here's the auto-generated Member class whose presentation is being customized.
import java.io.Serializable; import java.util.ArrayList; import java.util.List; import javax.annotation.Generated; import javax.validation.Valid; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonPropertyOrder; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.juneau.annotation.BeanProperty; @JsonInclude(JsonInclude.Include.NON_NULL) @Generated("org.jsonschema2pojo") @JsonPropertyOrder({ "username", "email", "picture", "adr", "bday", "tel", "title", "url", "skills", "profiles" }) public class Member implements Serializable { @JsonProperty("username") @BeanProperty("username") private String username; @JsonProperty("email") @BeanProperty("email") private String email; @JsonProperty("picture") @BeanProperty("picture") private String picture; @JsonProperty("adr") @BeanProperty("adr") @Valid private Adr adr; @JsonProperty("bday") @BeanProperty("bday") private String bday; @JsonProperty("tel") @BeanProperty("tel") private String tel; @JsonProperty("title") @BeanProperty("title") private String title; @JsonProperty("url") @BeanProperty("url") private String url; @JsonProperty("skills") @BeanProperty("skills") @Valid private List<Skill> skills = new ArrayList<Skill>(); @JsonProperty("profiles") @BeanProperty("profiles") @Valid private Profiles profiles; /** * * @return * The username */ @JsonProperty("username") @BeanProperty("username") public String getUsername() { return username; } /** * * @param username * The username */ @JsonProperty("username") @BeanProperty("username") public void setUsername(String username) { this.username = username; } public Member withUsername(String username) { this.username = username; return this; } /** * * @return * The email */ @JsonProperty("email") @BeanProperty("email") public String getEmail() { return email; } /** * * @param email * The email */ @JsonProperty("email") @BeanProperty("email") public void setEmail(String email) { this.email = email; } public Member withEmail(String email) { this.email = email; return this; } /** * * @return * The picture */ @JsonProperty("picture") @BeanProperty("picture") public String getPicture() { return picture; } /** * * @param picture * The picture */ @JsonProperty("picture") @BeanProperty("picture") public void setPicture(String picture) { this.picture = picture; } public Member withPicture(String picture) { this.picture = picture; return this; } /** * * @return * The adr */ @JsonProperty("adr") @BeanProperty("adr") public Adr getAdr() { return adr; } /** * * @param adr * The adr */ @JsonProperty("adr") @BeanProperty("adr") public void setAdr(Adr adr) { this.adr = adr; } public Member withAdr(Adr adr) { this.adr = adr; return this; } /** * * @return * The bday */ @JsonProperty("bday") @BeanProperty("bday") public String getBday() { return bday; } /** * * @param bday * The bday */ @JsonProperty("bday") @BeanProperty("bday") public void setBday(String bday) { this.bday = bday; } public Member withBday(String bday) { this.bday = bday; return this; } /** * * @return * The tel */ @JsonProperty("tel") @BeanProperty("tel") public String getTel() { return tel; } /** * * @param tel * The tel */ @JsonProperty("tel") @BeanProperty("tel") public void setTel(String tel) { this.tel = tel; } public Member withTel(String tel) { this.tel = tel; return this; } /** * * @return * The title */ @JsonProperty("title") @BeanProperty("title") public String getTitle() { return title; } /** * * @param title * The title */ @JsonProperty("title") @BeanProperty("title") public void setTitle(String title) { this.title = title; } public Member withTitle(String title) { this.title = title; return this; } /** * * @return * The url */ @JsonProperty("url") @BeanProperty("url") public String getUrl() { return url; } /** * * @param url * The url */ @JsonProperty("url") @BeanProperty("url") public void setUrl(String url) { this.url = url; } public Member withUrl(String url) { this.url = url; return this; } /** * * @return * The skills */ @JsonProperty("skills") @BeanProperty("skills") public List<Skill> getSkills() { return skills; } /** * * @param skills * The skills */ @JsonProperty("skills") @BeanProperty("skills") public void setSkills(List<Skill> skills) { this.skills = skills; } public Member withSkills(List<Skill> skills) { this.skills = skills; return this; } /** * * @return * The profiles */ @JsonProperty("profiles") @BeanProperty("profiles") public Profiles getProfiles() { return profiles; } /** * * @param profiles * The profiles */ @JsonProperty("profiles") @BeanProperty("profiles") public void setProfiles(Profiles profiles) { this.profiles = profiles; } public Member withProfiles(Profiles profiles) { this.profiles = profiles; return this; } @Override public String toString() { return ToStringBuilder.reflectionToString(this); } @Override public int hashCode() { return new HashCodeBuilder().append(username).append(email).append(picture).append(adr).append(bday).append(tel).append(title).append(url).append(skills).append(profiles).toHashCode(); } @Override public boolean equals(Object other) { if (other == this) { return true; } if ((other instanceof Member) == false) { return false; } Member rhs = ((Member) other); return new EqualsBuilder().append(username, rhs.username).append(email, rhs.email).append(picture, rhs.picture).append(adr, rhs.adr).append(bday, rhs.bday).append(tel, rhs.tel).append(title, rhs.title).append(url, rhs.url).append(skills, rhs.skills).append(profiles, rhs.profiles).isEquals(); } }
Great. Now any resource which returns a MemberHtml or a List<MemberHtml> to the Juneau HTML Serializer will see the freemarker template applied to any Adr instances within the appropriate cell.
BEFORE:
AFTER
: