Thymeleaf: How to use ${} in ${} [duplicate] - thymeleaf

I have a Hashmap (String, List<Offers>), passed to a Thymeleaf page. I am getting this map on the page and I can access it.
How can I do map.get(key) with Thymeleaf? I just need to fetch the values based on a certain key and then parse and print that value, which I know and have the logic for.
I am running a Broadleaf application and Thymeleaf is the UI engine for it.

Using ${map.get(key)} (where key is a variable) works for me.
${map['key']} only seems to work for String literal keys -- if the key you're looking up is a variable then ${map[key]} doesn't seem to work.
Accessing Map entries given a list of keys
Here's a full example, looking up items in a HashMap map given listOfKeys an ordered List of the keys of elements I want to get from the map. Using a separate listOfKeys like this lets me control the order of iteration, and optionally return only a subset of elements from the map:
<ul>
<li th:each="key: ${listOfKeys}"">
<span th:text="${key}"></span> = <span th:text="${map.get(key)}"></span>
</li>
</ul>
Looping through every entry in a Map
If you do not have an ordered list of keys, but just want to loop through every item in the Map, then you can loop directly through the map's keySet() (But you will have no control of the ordering of keys returned if your map is a HashMap):
<ul>
<li th:each="key: ${map.keySet()}">
<span th:text="${key}"></span> = <span th:text="${map.get(key)}"></span>
</li>
</ul>
This usage can be expressed even more concisely by simply iterating through the entrySet of the map, and accessing each returned entry's key and value members:
<ul>
<li th:each="entry: ${map}">
<span th:text="${entry.key}"></span> = <span th:text="${entry.value}"></span>
</li>
</ul>

You can simply use ${map.get('key')}

The way to access the value:
${map[__${key}__]}
You have to put the key between doubled underscores to make a pre-processing for the key variable.

I am using with drop down box the following for example to loop keys on maps
<select id="testId">
<option th:each="item: ${itemsMap}"
th:value="${item['key']}"
th:text="${item['value']}" />
</select>
in case of getting a specific value, I'm using
${itemsMap.get('key')}

In my situation, where I had a HashMap<String, String>, I had to do the lookup like this
<strong th:text="${map['__${entry.key}__']}"></strong>

The way to access the map value for a certain key keyaccess, assuming you have the map mymap in your model:
${mymap['keyaccess']}
That would give you the list associated to your entry, now you could iterate it.
In case you need, you could iterate a map in the same way you could iterate any other supported iterable objects, from the documentation:
Not only java.util.List objects can be used for iteration in
Thymeleaf. In fact, there is a quite complete set of objects that are
considered iterable by a th:each attribute:
Any object implementing java.util.Iterable
Any object implementing java.util.Map. When iterating maps, iter variables will be of class java.util.Map.Entry.
Any array
Any other object will be treated as if it were a single-valued list containing the object itself.

remarksMap is TreeMap and "id" is Long type value
<div th:if="${#maps.containsKey(remarksMap, id)}">
<textarea th:text="${remarksMap.get(id)}" rows="2" cols="30" maxlength="250"
autocomplete="off"></textarea>
</div>

All of the answers lead me in the right direction. The following code from (a table column detail) works:
<td>[[${statusesMap.get('__${employee.status}__')}]]</td>
statusesMap is a Map<String, String>
Employee is an employee class with a field called 'status'.
Note the single quotes. It did not work without them.

Related

thymeleaf th:if - Cannot index into a null value

thymeleaf index.xhtml -
EL1012E: Cannot index into a null value
<div class="mylist" th:each="row,rowStat : *{dataList}">
Folder: <span th:text="*{dataList[__${rowStat.index}__].folderName}" />
<div class="invalid-feedback"
th:if="${dataList[__${rowStat.index}__].folderName == appFolderName}">
Folder already exists. Please choose different folder name.
</div>
</div>
It is displaying the folderName but not validating th:if and appFolderName is a model attribute [dynamic value].
For the th:each="row,rowStat : *{dataList}" iterator, I would simplify that code to this:
th:each="row : ${dataList}"
You can think of this as being broadly equivalent to the following Java for-loop:
List<DataItem> dataList = ...; // assume this is populated with DataItems
for (DataItem dataItem : dataList) {
System.out.println(dataItem.getFolderName());
}
In the above for-loop, we do not need to access the list by index - and the same is also true for the Thymeleaf syntax.
Thymeleaf lets you access fields in an object without needing to refer to the getter method.
So, now that we have our row variable from th:each="row : ${dataList}", we can do this:
<div class="mylist" th:each="row,rowStat : *{dataList}">
Folder: <span th:text="${row.folderName}" />
<div class="invalid-feedback"
th:if="${row.folderName == appFolderName}">
Folder already exists. Please choose different folder name.
</div>
</div>
In the above code, you can see ${row.folderName} - which means Thymeleaf will invoke the getFolderName() method on the row object. This relies on your object using JavaBean naming standards for your getters.
You can enhance the Thymeleaf th:each processor by adding a second variable - which is what you do in your question: rowStat:
th:each="row,rowStat : ${dataList}"
This gives you access to extra information about the status of the Thymeleaf iterator - you can see a list of all these extra data values here.
These extra values are no needed in your case. But they can be useful in other situations - for example if you want to identify the first or last record in the list, or all even records, and so on.
Your example in the question uses the __${...}__ preprocessing syntax - which is very powerful and can be extremely useful. But again, it is not needed for your basic functionality.
Your example uses both ${...} and *{...} syntaxes to create Thymeleaf variables. It's important to understand the basic differences between them.
The difference is covered in the documentation describing the asterisk syntax:
the asterisk syntax evaluates expressions on selected objects rather than on the whole context. That is, as long as there is no selected object, the dollar and the asterisk syntaxes do exactly the same. And what is a selected object? The result of an expression using the th:object attribute.
The documentation has examples.
Finally, because you are using Spring (as per the tag in your question), then you are actually using Spring's dialect of Thymeleaf and SpEL - the Spring Expression Language.
This is broadly similar to the standard (OGNL) expression language used by the standard (non-Spring) Thymeleaf dialect - but it has several very useful enhancements.
One such enhancement is the safe navigation operator I mentioned in my comment:
${row?.folderName}
Here, the ? will immediately return null if row is null. Without this, you would get a null pointer exception when Thymeleaf attempted to invoke the getFolderName() method on a null row object.

Thymeleaf - converting a set to an ordered list

We have a Set<String> that we are printing to the page in Thymeleaf:
<h5>Unique Students names</h5>
<ol>
<div th:each="uniqueName: ${course.uniqueStudentsNames}">
<li th:text="${uniqueName}"></li>
</div>
</ol>
Since it is a Set<String> there is no order of the items in the set.
We like to convert the non-ordered Set<String> to an ordred List<String> so we will be able to print it by ABC. Since it is a UI issue, we don't like the backend to change its data structure.
An option we thought about is to create such a list in the Model:
model.addAttribute("course", course);
model.addAttribute("orderedList", /* convert Set to ordred List */);
But this seems very strange that the Model needs to handle such a UI issue.
Is there a way to do it in Thymeleaf?
Instantiate it as a TreeSet (you are probably using hashset?) and you have a set with the ordering behaviour you want. Set<String> (Set<anything> really) guarantees there won't be repeated items, but it does not imply anything about the order/unorder.
On the other side, thymeleaf, as a template engine, won't do such things as adding ordering behaviour to a collection that does not implement it. The most it will do is allow you to access methods of the collection; in the case of set: set utility methods.
If you no matter what want to use a non ordered set, you'd have to use something like javascript to manipulate the items on the client to order them.
Use the Thymeleaf utility method to convert the object to a List:
${#lists.toList(object)}.
Then perform a utility operation to sort as needed from there, optionally using your own Comparator.
The current Thymeleaf source code shows that the toList() method will check if the target object is an instance of Iterable. In your case, Set implements the interface.
To reference the external link:
if (target instanceof Iterable<?>) {
final List<Object> elements = new ArrayList<Object>(10);
for (final Object element : (Iterable<?>)target) {
elements.add(element);
}
return elements;
}

Grails Binding data from param with List values for each Key

Using Grails 2.4.4, I have a .gsp page that returns a parameter that looks like the following:
['update':'Update Budget',
'enteredAcct':['1018', '1200', '5301'],
'enteredOrg':['010', '010', '010'],
'enteredBudget':['100', '500', '200'],
'action':'updateBudget',
'format':null,
'controller':'orgs']
The number of values in the key's List is variable from 1..x (they will all be the same size though). Is there a cleaner way to save this information to the database?
My .gsp is:
<g:each in="${orgnList}" var="orgs">
<tr>
<td> ${orgs.acctCode}</td>
<td> <g:textField name="enteredBudget" value="${orgs.budget}"/></td>
<td> <g:hiddenField name="enteredOrg" value="${orgs.orgCode}"/> </td>
<td> <g:hiddenField name="enteredAcct" value="${orgs.acctCode}"/></td>
</tr>
</g:each>
Which displays an account and budget for a given Org and allows you to enter a new value for the "budget" only.
My controller is as follows:
(0..params.enteredAcct.size()-1).each{new Budget(
orgCode: params.enteredOrg[it],
acctCode: params.enteredAcct[it],
budget: params.enteredBudget[it],
activityDate: new Date()
).save(flush:true)}
Which works, however if my params only has 1 value for each key, then the closure doesn't work because enteredAcct (or any other key) is no longer a list, but a String.
I am mainly just looking to see if there is a more efficient way to save a returned params Map that has a list of values for each key, bindData doesn't seem to work in this case, which is probably expected.
I could always check if params.enteredAcct instanceof List then use that closure, and if not, just persist the single values, but I feel like there is a lot of noise in this code. I also don't like having to use flush:true if at all possible, but with that closure, the .save() won't seem to work unless the flush is forced.
Thanks
For this cases there's the method list in GrailsParameterMap.
params.enteredAcct // could be String or String[]
params.list('enteredAcct') // Always a List<String>
Another benefit of using it is that, for the "is multiple" case, you get list instead of an array (at least in Grails 2.2.x the original parameter value is an array).
As for the flush, you don't need it - but the data won't hit the database until the session is flushed, meanwhile the objects will remain in the Hibernate's session. A session is opened per request, so it's closed (and flushed) when the request is finished. It also gets flushed whenever a read operation is requested (DomainClass.findBy..., DomainClass.get..., etc). Flushing all the time is less performant, so I would avoid it.
You can use save(failOnError:true) though, to ensure the operation fails as soon as possible when provided with invalid data. And probably move it to a service to use their transactional features.

How to check for that map is containg the particular key using thymleaf

I am using the thymeleaf for presentation layer, so from controller i am sending the map containing key and list of values like this Map<Long,List<Long>> to x.html. But how i check whether the key contained in map in x.html using thymeleaf so please tell me the way to check it
i tried this way but not successfull
<span th:if="${#maps.containsKey(myMap, myStringValue)}">YEAH!</span>
There is such method you described since Thymeleaf version 1.0 which works for me as expected (see documentation). Maybe your Map key isn't String value or myStringValue isn't String.
Did you tried use constant String as key?
<span th:if="${#maps.containsKey(myMap, 'valueOfMyStringValue')}">YEAH!</span>
Or call Map#containsKey method directly on Map?
<span th:if="${myMap.containsKey('valueOfMyStringValue')}">YEAH!</span>

Struts2 - Iterating over embedded object lists in JSP

I have a class on the value stack that contains a list of objects, each object is another list of objects and finally there is the object I want to access. For Example:
FruitGroupsList-->FruitGroup-->Fruit
Let's say FruitGroupsList contains 2 FruitGroup Lists:
EdibleFruits
NonEdibleFruits
The EdibleFruits is a FruitGroup (list) that contains a bunch of Fruit Objects:
Apple
Pear
Plum
...
Each Fruit has a Name property.
I understand that I can Iterate through the FruitGroupsList like so:
<s:iterator value="FruitGroupsList" var="fgl">
<s:property value="%{#fgl.name}" />
</s:iterator>
How do I loop through each FruitGroup and get a property (such as FruitName)? I've tried various types of embedding other iterator tags but thus far I can't seem to solve this...
Thanks!
I'll assume you've only put one type in each list, otherwise you should really use a map.
So your veiw has access to your actions:
List<List<Fruit>> fruitGroupsList;
In which case you'd say:
<s:iterator value="fruitGroupsList">
<s:iterator>
<s:property value="whatEverPropertyAFruitHas"/> <!-- If this was a simple type such as String, you could just write <s:property/>
</s:iterator>
</s:iterator>
A list of lists requires two iterators. If things get more confusing then putting a getter in the action to extract some deeply nested items might be reasonable.
I have fixed myself. there was issue in Setter and getter of action class

Resources