Iterating over HashMap in Freemarker template displays map's methods - freemarker

In Apache OfBiz application, I have such code in controller:
public static String runRequest(HttpServletRequest request, HttpServletResponse response) {
Map<String, Long> typesToCount = getTypesToCount();
request.setAttribute("types", typesToCount);
return HttpFinals.RETURN_SUCCESS;
}
And in freemarker template it's processed/iterated like so:
<table
<#list requestAttributes.types as key, value>
<tr>
<td>${key}</td>
<td>${value}</td>
</tr>
</#list>
</table>
On rendered html page I'm getting both actual map's string key's and map's methods names (put, remove, add etc.).
As for values they are not rendered at all the with following error:
FreeMarker template error: For "${...}" content: Expected a string or something automatically convertible to string (number, date or boolean), or "template output" , but this has evaluated to a method+sequence (wrapper: f.e.b.SimpleMethodModel)
I'm using freemarker 2.3.28

Map.entrySet() method returns a collection (Set<Map.Entry<K, V>>) of the mappings contained in this map. So we can iterate over key-value pair using getKey() and getValue() methods of Map.Entry<K, V>. This method is most common and should be used if you need both map keys and values in the loop.
Try this code to iterate through the values in FTL
<table>
<#list requestAttributes.entrySet() as requestAttribute>
<tr>
<td>${requestAttribute.getKey()}</td>
<td>${requestAttribute.getValue()}</td>
</tr>
</#list>
</table>

Basically, I managed to iterate through the map only after wrapping it in SimpleMapModel like so:
public static String runRequest(HttpServletRequest request, HttpServletResponse response) {
Map<String, Long> typesToCount = getTypesToCount();
request.setAttribute("types", new SimpleMapModel(typesToCount, new DefaultObjectWrapper())));
return HttpFinals.RETURN_SUCCESS;
}
and int ftl template:
<#list requestAttributes.types?keys as key>
<tr>
<td>${key}</td>
<td>${requestAttributes.types[key]}</td>
</tr>
</#list>

That works like that if FreeMarker is configured to use a pure BeansWrapper (as opposed to DefaultObjectWrapper) for its object_wrapper setting, and the BeansWrapper.simpleMapWrapper property is left on its default value, false. Needless to say, it's a quite problematic configuration to work with. Hopefully it isn't the default of OfBiz. Although for old frameworks this can happen, as many many years ago this was a way of working around some limitations of FreeMarker, since this way you can just use the Java API of Map. Later, the introduction of the ?api built-in has made this hack needless.

Related

TemplateProcessingException: Nested variables, map and foreach loop

I have the following models,
public class Shift {
private UUID id;
private UUID unit;
private List employees;
private Timestamp startTime;
private Timestamp endTime;
...
}
public class Unit {
private UUID id;
private String name;
...
}
following route,
path("/shift", () -> {
get("", ShiftController.fetchShifts);
});
following controller,
public static Route fetchShifts = (Request req, Response res) -> {
Map map = new HashMap<>();
map.put("shifts", shiftDao.findAllByOrderByUnitAscStartTimeAsc());
map.put("units", unitDao.findAllByOrderByName().stream().collect(Collectors.toMap(Unit::getId, u -> u)));
return render(req, map, "shifts");
};
following template,
<table>
<tbody>
<tr th:each="s : ${shifts}">
<td th:text="*{units[__${s.unit}__].name}">unit</td>
</tr>
</tbody>
</table>
which gives me,
ERROR org.thymeleaf.TemplateEngine - [THYMELEAF][qtp1905797065-18] Exception processing template "shifts": Exception evaluating OGNL expression: "units[dd002ece-10c7-11e7-9009-93b58da4760f].name"
...
org.thymeleaf.exceptions.TemplateProcessingException: Exception evaluating OGNL expression: "units[dd002ece-10c7-11e7-9009-93b58da4760f].name"
...
Caused by: ognl.ExpressionSyntaxException: Malformed OGNL expression: units[dd002ece-10c7-11e7-9009-93b58da4760f].name [ognl.ParseException: Encountered " "c7 ""
...
and for the death of me I can't figure out the problem. What I want is to iterate through all the shifts and find out the name of the unit for each shift. For this I create a map units in the controller with the ids of units and objects representing them. However, I'm unsuccessful in implementing the map in the template. *{units.get(__${s.unit}__).name} in the template gives similar errors.
It should look like this:
<table>
<tbody>
<tr th:each="s: ${shifts}">
<td th:text="${units.get(s.unit).name}" />
</tr>
</tbody>
</table>
You have a few problems with your thymeleaf.
As the error message states, units[dd002ece-10c7-11e7-9009-93b58da4760f].name is not a valid expression. As far as I know, you can only use the ${map[index]} expression with numbers (which look like map[0]) and strings (which look like map['test']). Your expression is neither -- to the parser, you have a string missing the containing quotes.
Second, you're misusing __${}__ expressions. You should really only need to ever use __${}__ when you are defining a th:field expression. In most other cases, you should be able to do everything without them.

FreeMarker: Enumeration of Root

I am looking to enumerate the root object in FTL (freeMarker template language). for all hash arrays, and all sequence arrays, and all standard key value objects.
I need output in JSON format.
I have found the posted code for JSON enumeration:
http://ericbrandel.com/2013/03/28/freemarker-container-to-json/
<#macro objectToJsonMacro object>
<#compress single_line=true>
<#if object?is_hash || object?is_hash_ex>
<#assign first="true">
{
<#list object?keys as key>
<#if first="false">,</#if>
<#assign value><#objectToJsonMacro object=object<key> /></#assign>
"${key}" : ${value?trim}
<#assign first="false">
</#list>
}
<#elseif object?is_enumerable>
<#assign first="true">
[
<#list object as item>
<#if first="false">,</#if>
<#assign value><#objectToJsonMacro object=item /></#assign>
${value?trim}
<#assign first="false">
</#list>
]
<#else>
"${object?trim}"
</#if>
</#compress>
</#macro>
But the code does NOT WORK (throws an error) in the latest edition of FreeMarker.
I would appreciate any help that anyone can provide. Also, I don't see a way to "get" the root object.
Thanks,
Dan Linstedt
I solved this by adding a public method to my root object which JSON encodes itself. I used Gson to marshal the object.
/**
* #return JSON serialized version of this object.
*/
public String toJson()
{
GsonBuilder gsonBuilder = new GsonBuilder();
//gsonBuilder.setPrettyPrinting();
//gsonBuilder.disableHtmlEscaping();
// .. and whatever else you need Gson to do
return gsonBuilder.create().toJson(this);
}
I didn't need it in FreeMarker, but you could access it like:
${toJson()}

How to populate Form from Spring dropdown when form is submitted

My code lists the data that I would like to see, but when I hit submit it fails to populate the backing form with exception below. How can I make the bindings work? The exception I get is of Mismatch type, trying to insert a String in List when expecting Objects. Which makes sense.
Example
<tr>
<td><form:select id="myTypes" path="myTypes" multiple="false">
<form:option value="NONE" label="--- Select ---" />
<form:options items="${form.myTypes}" itemValue="id" itemLabel="label"/>
</form:select>
</td>
<td><form:errors path="myTypes" cssClass="error" /></td>
This is how form looks like
public class MyForm {
List<MyType> myTypes;
public List<MyType> getMyTypes() {
return myTypes;
}
public void setMyTypes(List<MyType> myTypes) {
this.myTypes = myTypes;
}
}
And of course MyType has id and label.
Link to above sample code and exception below
HTTP Status 500 - Request processing failed; nested exception is org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 1 errors
Field error in object 'Form' on field 'myTypes': rejected value [8768743658734587345]; codes [typeMismatch.Form.myTypes,typeMismatch.myTypes,typeMismatch.java.util.List,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [myForm.myTypes,myTypes]; arguments []; default message [myTypes]]; default message [Failed to convert property value of type 'java.lang.String' to required type 'java.util.List' for property 'myTypes'; nested exception is java.lang.IllegalStateException: Cannot convert value of type [java.lang.String] to required type [com.x.x.MyTypeEntity] for property 'myTypes[0]': no matching editors or conversion strategy found]
Solution:
Make sure you are mapping to Single element and not to the list :)
It should be something like
<form:select path="someEntity[${status.index}].myType.id">
HTH
I think the problem is that Spring does not know how to convert the selected option value (which is a String posted towards your app as an HTTP parameter named "myTypes" when you submit the form) to a MyType object. You should configure a Formatter< MyType > and register it to the Spring FormatterRegistry (see Spring doc) to let Spring know how to convert the incoming String to a MyType object.
public class MyTypeFormatter implements Formatter<MyType> {
#Override
public MyType parse(String text, Locale locale) throws ParseException {
return myTypeService.getType(text); // for example
}
public String print(MyType t, Locale locale) {
return t.getId();// for example
};
}
By the way, if I may, since your dropdown list is not multiple, it means that you are going to select just one of the available MyType options. The path of the < form:select > should be named "myType" instead of "myTypes" and especially, it should refer to a MyType attribute within your Form object and not to a List< MyType > attribute. Maybe you should name your first list of available MyType objects "availableTypes" and create a second attribute named "selectedType" to bind the MyType object corresponding to the selected option on the GUI.

Freemarker parse a String as Json

Probably it's not possible,
but I would like to transform a json string in a map with freemarker
ex:
<#assign test = "{\"foo\":\"bar\", \"f\":4, \"text\":\"bla bla\"}">
and be able to get the text key from this string
Use ?eval_json (requires FreeMarker 2.3.31):
<#-- Using '...' instead of "..." for convenience: no need for \" escapes this way. -->
<#assign test = '{"foo":"bar", "f":4, "text":"bla bla"}'>
<#assign m = test?eval_json>
${m.foo} <#-- prints: bar -->
<#-- Dump the whole map: -->
<#list m as k, v>
${k} => ${v}
</#list>
Before 2.3.31, ?eval was popular for this purpose, but that actually expects FreeMarker expressions. That's a problem because it doesn't support null, or \uXXXX escapes (so parsing of such JSON will fail). Also it can be a security problem, because it supports accessing variables, and calling methods/functions, while JSON doesn't.
freemarker.sourceforge.net/docs/pgui_datamodel_method.html
in code:
// a class to parse Json, just add this method to your rendered template data
// with data.put("JsonParser", new FreemarkerJsonParser());
// or in shared variables http://freemarker.sourceforge.net/docs/pgui_config_sharedvariables.html
public class FreemarkerJsonParser implements TemplateMethodModel{
#Override
public Object exec(List args) throws TemplateModelException {
return new Gson().fromJson(s, new TypeToken<Map<String, String>>() {}.getType());((String) args.get(0));
}
}
in the template:
<#assign map = JsonParser("{\"foo\":\"bar\", \"f\":4, \"text\":\"bla bla\"}")>
${map.text}
Sounds like you need to define/implement a template that reads JSON.

How to use two java bean to display data in a jsp

I want to display user's detail in a jsp. user's email, name is in one bean. which i am getting in a PagedListHolder. now i need date too which is in another bean . how can i add this date to PagedListHolder. i am using spring mvc framework
public String listUsers(Map<String, Object> map, #RequestParam(value="p", required=false) Integer p) {
PagedListHolder pagedListHolder = new PagedListHolder(usersService.listUsers());
map.put("date11", userActivity.getLastSeen_time());
int page = p;
pagedListHolder.setPage(page);
int pageSize = 2;
pagedListHolder.setPageSize(pageSize);
map.put("pagedListHolder", pagedListHolder);
System.out.println(":::::::::");
return "userListView";
}
in jsp i am using it as
<c:forEach items="${pagedListHolder.pageList}" var="user">
<tr onmouseover="ChangeColor(this, true);" onmouseout="ChangeColor(this, false);" onclick="DoNav('${pageContext.request.contextPath}/secure/detailUserView');">
<td>${user.name}</td>
<td>${user.email}</td>
<td>`DATE`</td>
</tr>
</c:forEach>
in jsp in place of DATE i need to display date which is in date bean
If the pagedListHolder stored as "pagedListHolder" in the map is available using ${pagedListHolder}, then the date, stored as "date11" in the map, must also be available using ${date11}.
You can format it using <fmt:formatDate value="{date11}"/>.

Resources