Attention advanced gurus Freemarker:
I want to use one freemarker template to be able to output arbitrary pojos tables, with columns displayed separately than data. The problem is that I cannot figure out how to get the function descriptor on pojo at runtime, and then freemarker call that function (lambda style). Due to the smoothing of the documents, it seems that Freemarker supports functional programming, but I seem to be unable to imagine the correct spell.
I hacked into a simplified case study. Let's say I have two lists: a list of people named firstName and lastName, as well as a list of cars with the make and model. I would like to display these two tables:
<table> <tr> <th>firstName</th> <th>lastName</th> </tr> <tr> <td>Joe</td> <td>Blow</d> </tr> <tr> <td>Mary</td> <td>Jane</d> </tr> </table>
and
<table> <tr> <th>make</th> <th>model</th> </tr> <tr> <td>Toyota</td> <td>Tundra</d> </tr> <tr> <td>Honda</td> <td>Odyssey</d> </tr> </table>
But I want to use the same template, as it is part of the structure that must deal with dozens of different types of pojo.
Given the following code:
public class FreemarkerTest { public static class Table { private final List<Column> columns = new ArrayList<Column>(); public Table(Column[] columns) { this.columns.addAll(Arrays.asList(columns)); } public List<Column> getColumns() { return columns; } } public static class Column { private final String name; public Column(String name) { this.name = name; } public String getName() { return name; } } public static class Person { private final String firstName; private final String lastName; public Person(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } public String getFirstName() { return firstName; } public String getLastName() { return lastName; } } public static class Car { String make; String model; public Car(String make, String model) { this.make = make; this.model = model; } public String getMake() { return make; } public String getModel() { return model; } } public static void main(String[] args) throws Exception { final Table personTableDefinition = new Table(new Column[] { new Column("firstName"), new Column("lastName") }); final List<Person> people = Arrays.asList(new Person[] { new Person("Joe", "Blow"), new Person("Mary", "Jane") }); final Table carTable = new Table(new Column[] { new Column("make"), new Column("model") }); final List<Car> cars = Arrays.asList(new Car[] { new Car("Toyota", "Tundra"), new Car("Honda", "Odyssey") }); final Configuration cfg = new Configuration(); cfg.setClassForTemplateLoading(FreemarkerTest.class, ""); cfg.setObjectWrapper(new DefaultObjectWrapper()); final Template template = cfg.getTemplate("test.ftl"); process(template, personTableDefinition, people); process(template, carTable, cars); } private static void process(Template template, Table tableDefinition, List<? extends Object> data) throws Exception { final Map<String, Object> dataMap = new HashMap<String, Object>(); dataMap.put("tableDefinition", tableDefinition); dataMap.put("data", data); final Writer out = new OutputStreamWriter(System.out); template.process(dataMap, out); out.flush(); } }
All of the above is predefined for this problem. So, here is the pattern I cracked. Pay attention to the comment in which I am having problems.
<table> <tr> <#list tableDefinition.columns as col> <th>${col.name}</th> </#list> </tr> <#list data as pojo> <tr> <#list tableDefinition.columns as col> <td><#-- what goes here? --></td> </#list> </tr> </#list> </table>
So col.name has the property name that I want to access from pojo. I tried several things, for example
pojo.col.name
and
<#assign property = col.name/> ${pojo.property}
but of course it doesn’t work, I just turned them on to help convey my intentions. I am looking for a way to get a function descriptor and call freemarker, or perhaps some kind of “evaluate” function that can take an arbitrary expression as a string and evaluate it at runtime.