Struts2 - Iterating over embedded object lists in JSP - struts2

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

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: How to use ${} in ${} [duplicate]

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.

Sorting of primefaces dataTable with p:columns and converter

I have a primefaces data table with dynamic colums:
<p:dataTable value="#{curSearch.getSearchResults()}" var="curSearchResult" sortMode="multiple" rowKey="#{curSearchResult.getUniqueId()}">
<p:columns value="#{curSearch.determinePrimaryPropertyNames()}" var="curPrimaryPropName" sortBy="#{curSearchResult[curPrimaryPropName].getValue()}">
...
</p:columns>
</p:dataTable>
In most cases the value of sortBy expressions fits (e. g. for a date or a string), but I have one special data type which must be translated. This means sortBy="#{curSearchResult[curPrimaryPropName].getValue()}" delivers a string which must be converted in a i18n string. This i18n string should be sorted then. Unfortunately the attribute sortBy throws an IndexOutOfBoundsExcpetion if the expression does not contain brackets []. My idea was to call a method and distinguish there between the "normal" values and the values which must be translated. So my 2nd idea is to define a converter for the value which has to be translated. But is converted value evaluated by primefaces? I guess not. Is there maybe another approach for this?
I'm using something like the following code snippet in a Lazy Loading data table and it's working fine.
<p:columns value="#{listController.ColumnModel}"
width="#{column.width}" var="column" field="#{column.field.fieldName}" sortBy="#{ticket[column.field.fieldName]}"
columnIndexVar="colIndex">
I'm storing the field name in the column model as you can see. In the backing bean method used for loading table data you will receive a list of sortmeta so you can sort your result set. I hope this can help you.

Binding value of UISelectOne and UISelectMany to same property

I'm trying to create a dynamic survey application in Prime Faces. I have a list of Question Objects that each contains a list of AnswerChoices. These are given to the f:selectItems value attribute. This is fine. The question object also contains a List of selectedValues which is given to the relevant selectOne/many component.
Because I'm looking to be generic, there will be questions that have multiple selected values and also some that have only one selected value. I wanted to be able to point the selectOne and selectMany components to the List of strings within the relevant Question object that represents the selectedValues.
This works ok for the selectMany component, but not for the selectOne component which needs to be pointed at a singular object rather than a list. Is there an easy way around this that I'm missing - as I'd like to only have one object representing the selectedValues if possible
You can use brace notation to bind the value to a list/array item at a specific index. The below example binds the value to the 1st item of the list/array.
<h:selectOneMenu value="#{bean.selectedAnswers[0]}" />
There's however a caveat: you need to prepare the list/array with the single item yourself during bean's (post)construction. JSF/EL won't do that in case of a <h:selectOneMenu>.
E.g.
#PostConstruct
public void init() {
selectedAnswers = new ArrayList<Answer>();
selectedAnswers.add(null);
}
It doesn't harm to reuse this preinitializated property for UISelectMany components by the way.

insert into instance in a repeat

my situation is the following:
I have a nodeset, through which I iterate and populate a table with some of the data
One of the fields, I do want to sum up
The problem:
Unfortunately I cannot use the sum method for the calculation as the nodeset is custom function that accesses data from other forms. And that seems to mess up things.
My idea of a solution:
I thought, I could create an instance and in each iteration add the value to it. Then I simply could access that data and do whatever calculation required. But I cannot get the xforms:insert to work.
A simplified version looks like this:
<xforms:repeat nodeset="(xxforms:si-source-forms('other_form'))">
<!-- table here -->
<xforms:insert
nodeset="instance('fr-form-instance')//positionen/position"
origin="instance('neue-position')"/>
</xforms:repeat>
The 'neue-position' instance contains bindings to the values in the source form:
<xforms:bind id="neue-position-binds" nodeset="instance('neue-position')">
<xforms:bind id="neue-position-bind" nodeset="position">
<xforms:bind id="neue-position-summe-bind" nodeset="summe" name="summe" type="xforms:string" required="true" xxforms:default="xxforms:si-source-forms('other_form')//gesamtbetrag_ausgabe" />
</xforms:bind>
</xforms:bind>
It does not work as expected though, so obviously something is wrong. I'd appreciate any hints.
About your first code snippet:
Your <xforms:insert> won't have any effet. You're in the view, and an action only runs if it is attached to an event listener. Without an ev:listener on the <xforms:insert> (or on an action around that insert), it just won't run.
About doing a sum over nodes not in an instance:
Assuming there is just one "sum" over the data returns by your custom function, you could write code along those lines:
Store the sequence of nodes returned by the function in an variable <xf:var name="others" ref="xxforms:si-source-forms('other_form')"/>
Use that variable in the repeat: <xf:repeat ref="$others"> (BTW, now XForms is standardizing the use of ref everywhere, in place of nodeset).
Do your calculation: <xf:var name="my-sum" ref="sum($others/path/to/values)"/>.
Finally, I imagine that you want to do something with $my-sum, maybe show it with an <xf:output>.

Resources