I am working on FreeMarker template for creating chart outputs using HighChart and we have a requirement to do double sorting based on 2 different columns.
Example: Sorting country list using 2 different columns "Region" and "Country"
I checked other forums and found double sorting has been defined within array elements as specified below but not at an individual element level.
current.children?sort_by('type')?sort_by(['properties','cm:name'])
Here is the code snippet
<#--Assign the array values-->
<#assign country = country + [ {
"category":"${level1}",
"value":level1Value?number,
"color": color?string,
"superRegion":"${level3?upper_case}",
"region":"${level2?upper_case}"
} ] >
Sorting the list - The below double sorting doesn't work. Does anyone know how to do this double sorting ?
<#list country?sort_by("superRegion")?reverse?sort_by("region") as countrySorted>
?sort_by can't sort by a composite "key" (as of 2.3.23). Applying ?sort_by twice just re-sorts the whole list.
Usually, lists should be sorted before passing them to the template anyway. I'm not sure if that's feasible in your case. If not, you can still write a TemplateMethodModelEx for it and put that into the data-model etc. (Plus I have made a note that ?sort_by should be able to do this, since it already exists anyway... maybe some contributor will pick it up.)
Resolved case like this. Using sort_by function twicely won't help you. I resolved this way: list, filter, sort_by. Here example.
Can be checked here: https://try.freemarker.apache.org/
Template:
Sorted by gender:
<#list humans?sort_by("gender") as h>
<#if !(genders??)>
<#assign prevGender = h.gender />
<#assign genders = h.gender + "---" />
</#if>
<#if prevGender != h.gender>
<#assign genders = genders + h.gender + "---" />
</#if>
<#assign prevGender = h.gender />
index: ${h.index}, gender: ${h.gender}, name: ${h.name}, age: ${h.age}.
</#list>
Sorted by gender, name:
<#list genders?split("---") as g>
<#if g?? && g?trim!="">gender: ${g}</#if>
<#list humans?filter(x -> x.gender = g)?sort_by("name") as x>
index: ${x.index}, gender: ${x.gender}, name: ${x.name}, age: ${x.age}.
</#list>
</#list>
Data model:
humans=[{"index":0,"gender":"male","name":"Bazil","age":34},{"index":1,"gender":"male","name":"Serhio","age":33},{"index":2,"gender":"male","name":"Andrew","age":32},{"index":3,"gender":"male","name":"Arty","age":34},{"index":4,"gender":"male","name":"Alex","age":33},{"index":5,"gender":"female","name":"July","age":30},{"index":6,"gender":"female","name":"Anna","age":31},{"index":7,"gender":"female","name":"Tatiana","age":32}]
You can overwrite your list:
<#assign mylist = mylist?sort_by("ID") />
<#assign mylist = mylist?sort_by("TYPETRAITEMENT") />
<#assign mylist = mylist?sort_by("DOMAINEMETIER") />
Related
I'm using a data-directive or list to pull in several values, of which, I don't know how many there will be, and I want to try and list through them, and create several variables in the list if you will.
<#list 1..10 as x>
<#-- the next line doesn't work, but what i'm trying to fix -->
<#assign .vars['VAR'+x?string] = rand(100) />
</#list>
But I can list them back out that way.
<#list 1..10 as x>
${.vars['VAR'+x?string]}
</#list>
The documentation for assign, says:
name: name of the variable. It is not expression. However, it can be
written as a string literal, which is useful if the variable name
contains reserved characters, for example <#assign "foo-bar" = 1>.
Note that this string literal does not expand interpolations (as
"${foo}").
Is there no way around this? Am I trying to do the impossible? Is there some way I can insert a derived name into the .vars... Hash is it?
A little more research that was close, but didn't get me there:
This prevoius question gives how to READ the derived variable, but I need to WRITE/CREATE the derived variable.
FreeMarker get variable value by concatenating another variable value
This prevoius question shows that I can use a string to assign a variable and re-iterates what we saw in the first link.
Variable name in Freemarker Template Language
As FreeMarker can't assign to elements of collections (but you can ?map(it -> ...) a collection to another), the only way is via ?interpret:
<#list 1..10 as x>
<#-- the next line doesn't work, but what i'm trying to fix -->
<#'<#assign VAR${x?c} = rand(100)>'?interpret />
</#list>
I wonder why do you need to assign to a dynamically named variables though, since reading dynamically named variables is also a pain, even if a lesser one.
Ultimately, I believe the correct way to phrase my solution was a sequence of hashes:
<#list 1..z_Coupon_Pet.NUM_COUPONS as x>
<#assign INSTORE_COUPON=call_coupon_from_table1() />
<#assign ONLINE_COUPON=call_coupon_from_table2() />
<#assign coupon_string_row= '{
"COUPON_NUM" : ${x},
"INSTORE" : "${INSTORE_COUPON?js_string}",
"ONLINE" : "${ONLINE_COUPON?js_string}"
}' />
<#if x==1>
<#assign coupon_hash_string = coupon_string_row />
<#else>
<#assign coupon_hash_string = coupon_hash_string + ',' + coupon_string_row />
</#if>
</#list>
</#if>
<#if coupon_hash_string?has_content>
<#assign coupon_hash=parsejson('[' + coupon_hash_string + ']') />
</#if>
We specifically avoid <#assign my_hash = my_hash + element /> because of this note in the documentation:
Note that hash concatenation is not to be used for many repeated concatenations, like for adding items to a hash inside a loop. While adding together hashes is fast and is constant time (independent of the size of the hashes added), the resulting hash is a bit slower to read than the hashes added together. Thus after tens... of additions the result can be impractically slow to read.
I need to iterate through a list made from the "split" built-in yet I haven't succeeded.
I have a two lists of dates that share some of the same dates. using "seq_index_of" as a "Vlookup" i'm able to find the correlation and get the index of the dates that are in both lists. however, "seq_index_of" gives a string, number, boolean or date/time values output and not a sequence (for me to iterate).
I use "split" to turn the string into a sequence. split wont truly do the job, though.
first i use seq_index_of for the two lists:
this will give me the index of correlated dates.
<#assign result>
<#list list1 as L1>
${list2?seq_index_of(L1)}
</#list>
</#assign>
---------------------------------------
next I iterate the result. for this, imagine "array" is the result from above:
ex 1. this does not work. cant iterate
<#assign array = "2020-10-02,2021-10-04,2022-10-04,2023-10-04" />
<#assign mappedArray_string = []>
<#list array?split(",") as item>
<#assign mappedArray_string += [item]>
</#list>
${"is_sequence: " + mappedArray_string?is_sequence?c}
<#--print the list-->
<#list mappedArray_string as m>
<#--i cant do ?string("MM/dd/yyyy") or ant other iteration
${m?string("MM/dd/yyyy")}
<#if m?has_next>${";"}<#else>${"and"}</#if>-->
<#with no iteration, this will work. not what i need-->
${m}
</#list>
+++++++++++++++++++++++++++++++++++++++++++++
ex 2. - this works with a regular numerical list
<#assign array = [100, 200, 300, 400, 500] />
<#assign mappedArray_numbers = []>
<#list array as item>
<#assign mappedArray_numbers += [item]>
</#list>
${"is_sequence: " + mappedArray_numbers?is_sequence?c}
<#--print the list-->
<#list mappedArray_numbers as m>
${m?string("##0.0")}<#-- able to iterate-->
</#list>```
expected:
date1 ; date2 ; date3 and lastdate
error:
For "...(...)" callee: Expected a method, but this has evaluated to a string (wrapper: f.t.SimpleScalar):
==> m?string [in template "preview-template" at line 27, column 3]
----
FTL stack trace ("~" means nesting-related):
- Failed at: ${m?string("MM/dd/yyyy")}
If you want to list the items of list2 that also occur in list1, then do this (requires FreeMarker 2.3.29 because of ?filter):
<#list list2?filter(it -> list1?seq_contains(it)) as d>
${d?string('MM/dd/yyyy')}
</#list>
Before 2.3.29:
<#list list2 as d>
<#if list1?seq_contains(d)>
${d?string('MM/dd/yyyy')}
</#if>
</#list>
I'm fairly new to Freemarker. I'm trying to create an email template that will list invoices, and then the total amount due at the bottom. However, there needs to be a different "Total Amount" based on the currency (some customers might have 3 invoices in EUR, 2 in GBP, etc).
I'm creating an associative array where the keys are the currency and the values are the total amount in that currency. For each invoice, I need to add the amount due to the value of the correct currency. But I'm getting an error that there is an unexpected character.
Here's the gist:
<#assign totalarr = {} />
<#list invoicelist as invoice>
<#assign invcur = invoice.currency />
<#assign invamt = invoice.amountremaining />
<#assign totalarr[invcur] = totalarr[invcur] + invamt />
</#list>
The error is in the second-to-last line, where I'm trying to add the amount to the total value. Any ideas?
Thanks!
-Kristin
To change hash subvariable value you need to use concatenation like this:
<#assign totalarr = {} />
<#list invoicelist as invoice>
<#assign invcur = invoice.currency />
<#assign invamt = invoice.amountremaining />
<#assign sum = totalarr[invcur]!0 />
<#assign totalarr = totalarr + {invcur : sum + invamt} />
</#list>
<#list totalarr?keys as key>
${key} = ${totalarr[key]}
</#list>
I have a list in free marker as below:
<#assign listVar = ["v1", "v2", "v3", "v4" ] />
From the above list I just want the sub list as v1 and v2.
I have been wandering to get the sub list in free marker. But couldn't managed to find.
Any help would be appreciate.
If you really want to make that slice based on indexes:
<#assign listVar = ["v1", "v2", "v3", "v4" ] />
<#assign sublistVar = listVar[0..1] />
See Freemarker Sequence slicing.
But beware, it will stop with error if the index is out of range. Depending on what you need this for, you may want to use ?chunk(2) instead.
Update: As of avoiding index-out-of-bounds error, in FreeMarker 2.3.21 you can issue listVar[0..*2], which will slice out 2 items, or less if there's less available. (Also exclusive-end slicing can come handy: listVar[0..<2])
You can use the index variable when you list the sequence.
<#assign listVar = ["v1", "v2", "v3", "v4" ] />
<#list listVar as aVar>
<#if aVar_index > 2><#break/></#if>
</#list>
You can also partition the sequence using chunk. This will split the sequence in multiple sequences of the given size.
<#assign partitions = listVar?chunk(2) />
<#assign firstPartition = partitions?first />
Source: FreeMarker Manual
However, it is better to filter the data before passing it to the template.
I have a list of beans and I would like to get a sub list matching a criteria on one or several properties with an expression as below:
${data[propertyName=='my value']}
data is a list of beans that have a property called propertyName.
Is such approach possible? If not, what is the best approach to do that.
Thanks very much for your answers.
Thierry
You could write a FTL function which selects the list items that match your criteria and collect them in a new sequence via sequence concatenation. Your filter function can be very simple or very sophisticated, it depends on your actual use case. Here is an example how it might work:
<#function filter things name value>
<#local result = []>
<#list things as thing>
<#if thing[name] == value>
<#local result = result + [thing]>
</#if>
</#list>
<#return result>
</#function>
<#-- some test data -->
<#assign data = [ {"propertyName":"my value", "foo":150},
{"propertyName":"other value", "foo":250},
{"propertyName":"my value", "foo":120}] >
<#assign filteredData = filter(data, "propertyName", "my value") >
<#list filteredData as item>
${item.foo}
</#list>
But keep into account that using sequence concatenation might be "suboptimal" for your performance.
You could do it by creating your own filter method variable and exposing it to the template. Then it would just be a matter of calling it with the list of beans and property value you want to filter on:
<#assign filteredData = filter(data, "my value") />
<#list filteredData as item>
// do something fancy
</#list>