Thymeleaf Map<String, Object> iteration and non-english chars in map key - java

I have Spring, Thymeleaf, HTML5 web page. And have problem iterating through Map if map object contains non-english characters.
All the character encoding filters/resolvers/converters are set and forced to UTF-8
Let's say I have the following setup:
Controller:
#RequestMapping(value = "/app", method = { RequestMethod.GET, RequestMethod.POST })
public String view(ModelMap model, #RequestParam(value = "foo", required = false) Integer foo) {
MapDTO mapDto = new MapDTO();
Map<String, List<Foo>> mapFoo ... // populate map etc.
model.add("mapDto", mapDto.setMapFoo(mapFoo))
return foo == null ? VIEW : VIEW + " :: fooFragment"
}
And template for testing purposes:
.. page ..
<div th:fragment="fooFragment" id="fooFragment">
..
<th:block th:each="fooMap : ${mapDto.mapFoo}">
<th:block th:each="item,row : ${fooMap.value.fooList}">
<p th:text="${item.val}"</p> <!-- working OK -->
<p th:text=" ${fooMap.value.fooList[__${row.index}__].idrValueName} "></p> <!-- working OK -->
<input th:field="*{mapDto.mapFoo[__${fooMap.key}__].fooList[__${row.index}__].val} "></p><!-- And this is working -->
<p th:text="${mapDto.mapFoo[__${fooMap.key}__].fooList[__${row.index}__].val} "></p><!-- FAILING ON THIS -->
</th:block>
</th:block>
..
</div>
.. rest of the page ..
Let's say, that i have map with keys "A" and "Ā".
I'm getting following exception:
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.thymeleaf.exceptions.TemplateProcessingException: Exception evaluating SpringEL expression: "mapDto.mapFoo[Ā].fooList[0].val"
..
Caused by:
java.lang.IllegalStateException: Cannot handle (257) 'Ā'
Whats interesting - I have almost exactly the same setup on different page (A bit different mapDto structure and template, but the same principle, same page headers) and I don't have the same issues there.
Maps that don't have keys with non-english characters behaves as predicted.
Any ideas where lies the problem?
SOLVED:
Solved described issue using double-bracket syntax
${{...}}
There are some limitations with it. But I was able to work around them it solved my main issue.

mapDto.mapFoo[Ā].fooList[0].val
This expression contains accented character Ā. Looking at the source code of Tokenizer, accented characters are clearly indicated as an invalid candidate for tokenization.
Spring EL's InternalSpelExpressionParser which is responsible for parsing expressions is tightly coupled to this Tokenizer, so I don't think there is an out of the box way to allow tokenization of accented characters. I'm pretty sure there is a good reason why this is not allowed (e.g. it might break other parts of the framework) so I suggest you go along to what the framework is demanding and remove the accented characters in the expression.

Related

How to change Thymeleaf fragments dynamically depending on the href link

I want to build a page with Thymeleaf fragments, which are rendered dynamically depending on the followed link.
And I have an issue with resolving fragments names in runtime through the Spring controller.
Here is the minimal reproductive example to show what I mean.
I have list.html page with two links List 1 and List 2 with pic and code as below:
<body>
<button type="button"><a th:href="${'/lists/list1'}">List 1</a></button>
<button type="button"><a th:href="${'/lists/list2'}">List 2</a></button>
<!-- This doesn't work, the include is not resolved correctly -->
<th:block th:include="'fragments/lists/' + ${fragmentName}
+ ' :: ' + ${fragmentName}"></th:block>
<!-- However static include works well -->
<th:block th:include="fragments/lists/list1 :: list1"></th:block>
</body>
The related Spring controller looks as:
#GetMapping("lists")
public String getListsPage(Model model) {
model.addAttribute("fragmentName", "listAll");
return "lists";
}
#GetMapping("lists/list1")
public String getAllItems(Model model) {
model.addAttribute("list1", itemService.getList1());
model.addAttribute("fragmentName", "list1");
return "lists";
}
#GetMapping("lists/list2")
public String getAllItems(Model model) {
model.addAttribute("list2", itemService.getList2());
model.addAttribute("fragmentName", "list2");
return "lists";
}
The problem that fragmentName is not being resolved at runtime and it throws TemplateInputException exception:
Caused by: org.thymeleaf.exceptions.TemplateInputException: Error resolving
template ['fragments/lists/' + ${fragmentName} + '], template might not exist
or might not be accessible by any of the configured Template Resolvers
(template: "lists" - line 38, col 11)
At the same time static block works correctly as shown in list.html page code.
Please don't suggest me Spring MVC 3.2 Thymeleaf Ajax Fragments, I don't want to use AJAX, I found the current solution of returning the fragment name using controller very clear and simple for my case.
Probably I can use Fragment Expressions, but I am not sure how exactly.
Any suggestions are appreciated.
I would express the syntax like this:
<th:block th:include="~{${'fragments/lists/' + fragmentName} :: ${fragmentName}}"></th:block>

Thymeleaf template and Spring Boot : Creating a radio input from Java enum

I would like to populate a radio input control in a Thymeleaf automatically from a Java enum type called "source". I am using Spring Boot in the backend.
My controller initialises a list of the enum values like this:
this.sourcesAsList = Arrays.asList(Source.values());
model.addAttribute("sources", sourcesAsList);
This works fine, at least as far as the List is concerned (as I can see in the log output).
The Thymeleaf template then tries to instantiate a radio control based on this attribute in the model, like so:
<div th:each="source : ${sources}">
<input type="radio" th:field="*{source}" th:value="${source.value}"/><label th:text="| ${source.value} |">Should not see this !</label>
</div>
However, when I try to load this page, I get the following error:
[Request processing failed; nested exception is org.thymeleaf.exceptions.TemplateInputException: An error happened during template parsing (template: "class path resource [templates/feedbackapp2.html]")] with root cause
java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'source' available as request attribute
The enum is pretty simple:
public enum Source {
VALONE, VALTWO, VALTHREE, VALFOUR;
public String getName() {
return this.name();
}
}
It's the first time I work with Thymeleaf, so I guess it is just a simple syntax problem somewhere, but even after googling I couldn't find a working example. Any ideas? Is it possible at all to do this with an enum? If not, what kind of datatype would be more appropriate?
Thanks a lot.
Cheers,
Martin
I played around a bit more and got it working. The following HTML snippet displays the radio buttons correctly based the list of Enums and also is wired to the model correctly, in that I received the selected value in the controller's POST method:
<div th:each="source : ${sources}">
<input name="source" type="radio" th:value="${source}"/><label for="source" th:text="| ${source} |">Something is wrong !</label>
</div>
There were two problems:
it is not necessary to access the name() attribute of the enum (so,
using ${source} instead of ${source.name} is fine)
instead of th:field, use the namne attribute of the input control
Thanks a lot to Periklis for the comment.

Bind list or array to form in Thymleaf

On my website I have a few checkboxes each of them contains id in value attribute. After submitting form I'd like to have a list containing ids of checked checkboxes to be passed to the controller. That's how I want to make new page comparing n products.
Controller can accept List<Long> or long[]. That's what I have for now:
HTML:
<form th:action="#{/comparison}" th:object="${productsComparison}" target="_blank" method="post">
<table>
<tr data-th-each="item, iter : ${items.item}">
<td>
<input type="checkbox" th:name="|productsComparison.ids[${iter.index}]|" th:value="${item.id}"/>
</td>
</tr>
</table>
I've added to my controller List<Long> wrapped in ProductComparison with appropriate getters and setters. After submitting form list is always null.
Controller:
#RequestMapping("/productsPage")
public String showProducts(Model model) {
ProductsComparison productsComparison = new ProductsComparison();
model.addAttribute("productsComparison", productsComparison);
}
#RequestMapping("/comparison")
public String compareProducts(#ModelAttribute ProductsComparison productsComparison) {
System.out.println("List: " + productComparison.getIds());
// Always shows null
return "comparison";
}
public class ProductsComparison {
private List<Long> ids;
// Getters & setters
}
The underscores __ are ThymeLeaf's syntax for a preprocessing expression. Or in layman's terms, the stuff inside of the underscores are processed before the rest of the expression.
This is important because your expression is using an array, and the ${iter.index} portion of it gives the array index.
If you are curious or if anyone else stumbles upon this. th:name is no different than the html attribute name. The ThymeLeaf engine will just shove a name attribute into the html entity or overwrite the old one. ThymeLeaf has a ton of properties like this. th:field is a totally different beast though. It is what tells ThymeLeaf to bind the input of your form to the back forming bean or your model attribute, th:object="${productsComparison}". So with the expression th:field="*{ids[__${iter.index}__]}", Thymeleaf is going to call productsComparison.getIds[0] and the corresponding setter.
Note about th:name if you are submitting with Javascript of any kind, you probably want to use this. Most likely you are serializing your form, and the JSON created by this method call will use the name property.
If you want to know more about ThymeLeaf preprocessing expressions, there is a bit in the documentation about it.
I've finally found solution. Instead of th:name I had to use th:field.
th:field="*{ids[__${iter.index}__]}"
Because it's field I didn't have to specify object productsComparison before ids. ids field is List<String>.
To be honest I have no idea what underscores do, I will be glad if someone could explain.

Striving to understand Spring MVC model and specific controller method

I have encountered the following code in a Spring-based Java application.
This is the controller:
#RequestMapping(value="/plants/form")
public String form(Model model){
model.addAttribute("plant", new Plant());
return "plants/create";
}
I have several questions regarding this snippet.
I know that form method is called for requests to /plants/form,
but I would like to know which method invokes form and passes arguments to it?
To me, it seems that a model is like a database. So, it looks like
model.addAttribute("plant", new Plant()); creates a Plant
instance and makes it accessible in a model under the name plant.
Thus, an attribute of a model seems to be
something like a field in DB. What is the exact meaning of
an attribute of a model?
What is value in #RequestMapping(value="/plants/form") and what
is the difference between aforementioned notation and
#RequestMapping("/plants/form")?
Consider this incomplete snippet of HTML:
<form class="form-horizontal" role="form" th:object="${plant}" th:method="post" th:action="#{/plants}">
<div class="form-group">
<label for="name" class="col-sm-2 control-label">Name</label>
<input type="text" th:field="*{name}"></input>
</div>
</form>
What is the meaning of $, *, # in "${plant}","*{name}" and
"#{/plants}". It is not clear to me.
1) If I understand your question correctly, then Spring MVC DispatcherServlet is responsible for routing request to your handler method based on configured HandlerMapping/HandlerAdapter.
2) Model is M in MVC, it has nothing in common with relational database. Rather consider it as a Map of String keys to Object values. Spring MVC model attributes are stored in request scope under the hood.
3) There is no difference, value is the attribute of #RequestMapping annotation. When you want to pass a single value argument and no other arguments, then it is possible to omit value by convention.
4) These are tags and attributes of Thymeleaf Standard and SpringStandard dialects. More information here.

Thymeleaf Error for <input> date with th:pattern

I'm trying to use th:pattern for date input form field like following for a thymeleaf template using spring-mvc but without luck. Anybody else experienced similar thing and has some insight or alternative?
I tried 1. hard-coding the pattern
<input type="text" th:pattern="MM/dd/yyyy" th:field="*{classDate}"/>
received Error:
Request processing failed; nested exception is org.thymeleaf.exceptions.TemplateProcessingException: Could not parse as expression: "MM/dd/yyyy"
And 2. setting pattern in java code for template to use
<input type="date" th:pattern="${classdate_format}" th:field="*{classDate}"/>
received Error:
Request processing failed; nested exception is org.thymeleaf.exceptions.TemplateProcessingException: Error during execution of processor 'org.thymeleaf.spring3.processor.attr.SpringInputGeneralFieldAttrProcessor'
pattern is html5 attribute of input tag.
pattern validates an input value using regex. So that value, which you inserts into pattern attribute should be correct regex pattern.
If you are using Thymeleaf's th: prefix, template processor trying to find appropriate variable in Spiring's model and insert it as a value of attribute. Thymeleaf is using Spring EL for it's templates.
So your first approach is incorrect because of using invalid SpringEL expression.
The second solution looks better, type="date" gives you exactly what you want, but works not for all browsers. ${classdate_format} looks like correct expression. To understand what causes the second error more code needed.
Anyway is there any reason to use th: prefix for pattern attribute? It needed only if you want to create regex pattern dynamically at server side. But in this case regex pattern is pretty straightforward, so you can use attribute without th:. To write correct regex for your case please refer to this answer.

Categories

Resources