Search code examples
javatemplatesfreemarker

How to create lists of subdata to loop through in Freemarker


I'm looking for a way to generate a grouped by report using Freemarker based on the dataType (int) from a list of data items.

So for example I have:

data.datatype = 1 where data has id = 1
data.datatype = 1 where data has id = 4
data.datatype = 1 where data has id = 7
data.datatype = 3 where data has id = 2
data.datatype = 3 where data has id = 3
data.datatype = 4 where data has id = 5
... and so on

The sequence is sorted by datatype but at the same time I need to keep the data items in their respective order (secondary sort order).

I'm also using macros like functions. Ideally I'd love to have <#macro rendertable filteredDatatypelist datatype>

In the past I did called a macro to render the header, footer, and row based on the current datatype but I'm trying to move away from that and have a single macro to render the whole table on the sublist for each datatype.

That is to say send the macro a list of the data items for the datatype. So for example in the above data I would call the rendertable macro 3 times, once for datatype 1, 3, and 4.

I looked at trying to create a sublist using filter but this seemed like it could be a performance issue with O(n) for each datatype. Not ideal but even then I'm not sure how it would work exactly. Also please note that I have no idea how many datatypes there are or what their values could be. I also looked at seq_index_of and seq_last_index_of but again I don't know what the different datatypes will be. I considered creating a list of unique datatypes first but I have no idea how to do that either. In other words I'm not sure how to even do something like

<#assign dataForDataType = dataList?filter(data.datatype = 1)>
<#list dataForDataType as x>$<@renderTable x 1></#list>

Except that in the case above I'm hardcoding the datatype and it only works for one datatype that is hardcoded. Even if it's O(n) I think I would be ok with it but the issue is that I don't know how to do it for a list of datatypes. Any help on how this can be achieved would be greatly appreciated.


Solution

  • What I ended up doing was creating a class that extended the interface TemplateMethodModelEx that would take in the list of data and then return a list of datatypes. I then looped through the datatypes and then using another function got the list of data items for a specific datatypes through another TemplateMethodModelEx.

    Something along the lines of:

    <#assign datatypes = getDistinctDataTypes(dataList)/>
    <#list datatypes as datatype>
        Datatype: ${datatype.id}
        <#list getDataForDatatype(dataList, datatype) as data>
            ${data.whatever}
         </#list>
    </#list>
    

    Then I have classes such as this example:

    public class GetDistinctDataTypes implements TemplateMethodModelEx {
        @Override
        public Object exec(List arguments) throws TemplateModelException
        {
            // Testing code is omitted for brevity and everything is assumed to just work and be correct.
            DefaultListAdapter defaultListAdapter = ((DefaultListAdapter)arguments.get(0));
            List<Data> dataList = (List<Data>)defaultListAdapter.getWrappedObject();
            // Do what I need and return list of datatypes
            return getDistinctDataTypesListFromDataList(dataList);
        }
    }
    

    I then also added the method to the Configuration so that it can be used. You can do it in the freemarker file but I find it's better in the Java code.

    CONFIGURATION.setSharedVariable("getDistinctDataTypes", new GetDistinctDataTypes());
    

    You can find more details on the following Freemarker document page about method directives.