Thymeleaf #lists.contains() expression utility not working
Asked Answered
N

4

11

I'm working with the thymeleaf standard dialect and trying to render a list of checkboxes in a form. The rendering is ok, however, the problem is where I try to apply the "checked" property to the checkboxes using the thymeleaf #lists.contains() expression utility method.

So I have a model class that has the following fields:

private List<Template> templates;

@FormParam("selectedTemplates")
private List<String> selectedTemplates = Lists.newArrayList();

A Thymeleaf template html fragment:

<div th:each="template : *{templates}">
    <input type="checkbox" name="selectedTemplates" th:value="${template.id}" 
    th:checked="${#lists.contains(product.selectedTemplates, template.id)}" />
    <label th:text="${template.filename} + ' (' + ${template.description} + ')'" />
    <!-- Attempt to use the list contains to check the field -->
    <div th:text="${product.selectedTemplates}"/>
    <div th:text="${template.id}"/>  
    <div th:text="${#lists.contains(product.selectedTemplates, template.id)}" />
</div>

The output on the page for one of the checkboxes that should be selected.

<input type="checkbox" name="selectedTemplates" value="4" /> (Template Name)
<div>[4,5]</div>
<div>4</div>
<div>false<div>

So as you can see, I print the list which has values [4,5] and I use the #lists.contains method to see if it has template.id in it, however, the method always returns false. I even tried some hard coded ids to test the method and I always get "false" back.

For example:

<div th:text="${product.selectedTemplates}"/>
<div th:text="${#lists.contains(product.selectedTemplates, 4)}" />

Prints [4,5]false

<div th:text="${product.selectedTemplates}"/>
<div th:text="${#lists.contains(product.selectedTemplates, '4')}" />

Prints [4,5]false

Not sure what I'm doing wrong, but it seems so straight forward, not sure what else to try. My guess is there is something wrong with the syntax. Any suggestions or advice is greatly appreciated. I'm not able to find any resources on troubleshooting this problem, the thymeleaf guide quickly glosses over that section.

Nissie answered 18/12, 2013 at 19:20 Comment(0)
N
22

I had posed this answer in thymeleaf forum and got some help to determine the root cause and resolution. See [http://forum.thymeleaf.org/Problem-with-thymeleaf-expression-utility-lists-contains-td4027317.html][1]

Essentially, the single character strings are being interpreted as a character type by default and therefore never match any of the strings in the list. Multi-character strings evaluate to a string type therefore work as expected. By encapsulating the value being searched in html encoded quotes, the parser is forced to evaluate the single character string as a string type instead of a char type. For example:

<div th:text="${#lists.contains(testList, &quot;3&quot;)}"/>
<div th:text="${#lists.contains(testList, &quot;P&quot;)}"/>

Just wanted to post this in case anyone was interested in the root cause and solution.

Nissie answered 5/8, 2014 at 6:0 Comment(0)
R
8

i know the question is old, but i post this answer so can be useful to others users having the same problem.

i don't know if it's you or another user, but here i found that we must add

'' + template.id

so:

th:checked="${#lists.contains(product.selectedTemplates, '' + template.id)}" 

for me it worked! Thank you

Rotberg answered 11/7, 2014 at 9:18 Comment(0)
N
3

I did some further experimentation and found an issue where the #lists.contains() utility method does not work for a single character string. I would add that I'm using thymeleaf 2.0.19, but also tried 2.1.1.RELEASE with the same result.

I created a simple list in my model and added some values:

@FormParam("testList") 
private List<String> testList = Lists.newArrayList(); 

testList.add("test1"); 
testList.add("test2"); 
testList.add("3"); 
testList.add("P"); 
testList.add("33");

Then tested the #lists.contains() method like so:

<div th:text="${product.testList}"/>
<div th:text="${#lists.contains(product.testList, 'test1')}"/>
<div th:text="${#lists.contains(product.testList, 'test2')}"/>
<div th:text="${#lists.contains(product.testList, '3')}"/>
<div th:text="${#lists.contains(product.testList, 'P')}"/>
<div th:text="${#lists.contains(product.testList, '33')}"/>

And the output is as follows:

<div>[test1, test2, 3, P, 33]</div>
<div>true</div>
<div>true</div>
<div>false</div>
<div>false</div>
<div>true</div>

So, clearly the method does not work for single character strings. Because I'm working on a new project I simply reset the sequences driving these ids so that I don't have any single character id's to work with. That is certainly not the solution I was hoping for but it works. I will also add that in order to get the method to work in the context of my question, I had to add an empty character to my id like so:

th:checked="${#lists.contains(product.selectedTemplates, '' + template.id)}"

Without it, the contains method would return "false" because template.id is a Long type.

Nissie answered 19/12, 2013 at 18:42 Comment(3)
That is pretty strange. As far as i can see in the code of ListUtils the contains method is merely calling contains on the target list. In your case that would be equals to ${product.targetList.contains('3')}. Can you check if this gives the result correctly? In case of 3 i could imagine that its suddenly treated as an int and then the list would really not contain it, but with the 'P' that should return really true.Glasshouse
Or even worse the 'P' is treated as a char. No clue if this would return true for a string list containsGlasshouse
Thanks for the feedback Martin, I tried the code you suggested: ${product.testList.contains('3')}, ${product.testList.contains('P')}, ${product.testList.contains('33')} And the results were the same: "false", "false", "true" so perhaps it is that it is trying to infer a type incorrectly and thus not working with a single character.Nissie
G
0

As i can see your selectedtemplates contains a list of templates and not ids, right?

If thats the case you should check the list like this:

<input type="checkbox" name="selectedTemplates" th:value="${template.id}" th:checked="${#lists.contains(product.selectedTemplates, template)}" />
Glasshouse answered 19/12, 2013 at 6:41 Comment(1)
List<String> selectedTemplates is only the id's not the "template" entity. I hadn't thought of going that route. I found a work around which I will post in an answer.Nissie

© 2022 - 2024 — McMap. All rights reserved.