I would like to create a list of up to three items using a template fragment. Three spaces for items will always be displayed regardless of whether or not there is an item, so it looks something like this.
<div>
<div th:if="${#lists.size(block.children) > 0}"
th:insert="code-block :: block(${block.children[0]})"
th:remove="tag">
</div>
</div>;
<div>
<div th:if="${#lists.size(block.children) > 1}"
th:insert="code-block :: block(${block.children[1]})"
th:remove="tag">
</div>
</div>;
<div>
<div th:if="${#lists.size(block.children) > 2}"
th:insert="code-block :: block(${block.children[2]})"
th:remove="tag">
</div>
</div>
However, even though the th:if statement evaluates as false with an empty list, it still attempts to execute the `th:include statement, giving me the following error:
Caused by: org.springframework.expression.spel.SpelEvaluationException:
EL1025E:(pos 14): The collection has '0' elements, index '0' is invalid
How can I get the if statement to take precedence over the fragment execution?
Yeah, unfortunately since include has precedence over if, you're going to have to move the if higher up. The simplest way would be to put it in a th:block, like this:
<div>
<th:block th:if="${#lists.size(block.children) > 0}">
<div th:insert="code-block :: block(${block.children[0]})" th:remove="tag" />
</th:block>
</div>;
<div>
<th:block th:if="${#lists.size(block.children) > 1}">
<div th:insert="code-block :: block(${block.children[1]})" th:remove="tag" />
</th:block>
</div>;
<div>
<th:block th:if="${#lists.size(block.children) > 2}">
<div th:insert="code-block :: block(${block.children[2]})" th:remove="tag" />
</th:block>
</div>
You could also probably simplify your code to look something more like this:
<th:block th:each="i: ${#numbers.sequence(0, 2)}">
<th:block th:if="${#lists.size(block.children) > i}">
<div th:insert="code-block :: block(${block.children[i]})" th:remove="tag" />
</th:block>
<th:block th:unless="${i == 2}">;</th:block>
</th:block>
Related
I'm creating a couple of thymeleaf fragments to use in other pages, and I was told they need to be inside a well-formed html page (with HTML, HEAD, BODY tags etc). Is this the case?
The fragments are only going to be used with th:include/th:replace in other places.
A fragment just needs to be a well formed html. You can start with a div
For e.g
<div th:fragment="formField (field, value, size)">
<div>
<label th:for="${#strings.toLowerCase(field)}"> <span
th:text="${field}">Field</span>
</label>
</div>
<div>
<input type="text" th:id="${#strings.toLowerCase(field)}"
th:name="${#strings.toLowerCase(field)}" th:value="${value}"
th:size="${size}">
</div>
Which you then include somewhere else
<body>
<header th:insert="fragments/general.html :: header"> </header>
<div th:replace="fragments/forms.html
:: formField(field='Name', value='John Doe',size='40')">
</div>
<div th:replace="fragments/general.html :: footer"></div>
I took these examples from here: https://www.baeldung.com/spring-thymeleaf-fragments
I have following template with fragment where I am passing variable:
<div th:fragment="main">
<span>
<th:block th:include=" :: inner(${item})" />
</span>
<a>
<th:block th:include=" :: inner(${item})" />
</a>
</div>
<th:block th:fragment="inner(item)">
[[${item.name}]]
</th:block>
If I try to render it I get an error saying:
Property or field 'name' cannot be found on null
If I render it like this
<div th:fragment="main">
<span>
<th:block th:include=" :: inner(${item})" />
[[${item.name}]]
</span>
<a>
<th:block th:include=" :: inner(${item})" />
</a>
</div>
What am I doing wrong when assigning variable to fragment?
Figured it out.
The reason of error was that the 'inner' fragment was rendered during the render along with main fragment. So along with inclusions if was rendered by itself. And this last render spoiled it.
Possible solutions are to:
move inner fragment to another file
add th:if="false" to 'inner' fragment to avoid self rendering (inclusions will continue to work).
I want to change my Page Layout according to how many files are existing.
I developed the following code:
<f:for each="{files}" as="file">
<div class="ce-textpic-file-download-wrapper">
<a href="/fileadmin/{file.identifier}" target="_blank">
<div class="filesize">({file.originalFile})</div>
</a>
</div>
</f:for>
Now there are different cases:
No files given -> empty div
1 File given -> Just show the one File
2 Files given -> Add Row with two columns, each column is col-6 and contains the file
3 Files given -> row(col-6, col-6), row(col-6)
4 Files given -> 2 rows, each with 2 col-6
5 or more files -> Just show the first 4 Files (as described in "4
files given")
How can i accomplish something like this.
I am using Typo3-9.5.5 btw
Is this a possible solution?
<f:switch expression="{files -> f.count()}">
<f:case value="0">EMPTY</f:case>
<f:case value="1">ONE</f:case>
<f:case value="2">TWO</f:case>
<f:case value="3">THREE FILES</f:case>
<f:case value="4">FOUR</f:case>
<f:defaultCase>MORE THAN FOUR</f:defaultCase>
</f:switch>
How can i address for exmaple the third element in {files} does it work like this: {files}[2]?
EDIT:
I solved it like this (There was no need for more than one row class):
<f:if condition="{files}">
<f:if condition="{files->f:count()} == 1">
<f:then>
<div class="row download-row">
<div class="col-lg-6 col-md-12 col-xs-12">
<div class="ce-textpic-file-download-wrapper ">
<a href="/fileadmin/{files.0.identifier}" target="_blank">
<div class="fileinfo">
{files.0.originalFile.properties.name}<br>
({files.0.originalFile.properties.size})
</div>
</a>
</div>
</div>
<div class="col-6"></div>
</div>
</f:then>
<f:else>
<div class="row">
<f:for each="{files}" as="file" iteration="iterator">
<f:if condition="{iterator.index} < 4">
<div class="col-lg-6 col-md-12 col-xs-12">
<div class="ce-textpic-file-download-wrapper">
<a href="/fileadmin/{files.iterator.identifier}" target="_blank">
<div class="fileinfo test">
{file.originalFile.properties.name}<br>
({file.originalFile.properties.size}
</div>
</a>
</div>
</div>
</f:if>
</f:for>
</f:else>
</f:if>
</f:if>
Depending on your variety of cases you might use different f:for viewhelper or change just the rendering inside of one single f:forviewhelper for all.
But you also should make use of the information an iterator will give you inside the f:for viewhelper.
You also might use a f:cycle viewhelper, so you do not need to caclulate modulo.
Number of files: {files->f:count()}<br />
<f:for each="{files}" as="file" iteration="iterator">
<f:cycle values="{0: 'odd', 1: 'even'}" as="cycler">
<f:debug title="inside the loop">{file:file, iterator:iterator, cycler:cycler}</f:debug>
</f:cycle>
</f:for>
EDIT:
regarding your cases I think you must consider two variants:
<f:if condition="{files}">
<f:if condition="{files->f:count()} == 1">
<f:then>
<f:render section="item" arguments="{file:file}" />
</f:then>
<f:else>
<f:for each="{files}" as="file" iteration="iterator">
<f:if condition="{iterator.index} < 4">
<f:if condition="iterator.isOdd"><div class="row"></f:if>
<div class="col-6">
<f:render section="item" arguments="{file:file}" />
</div>
<f:if condition="iterator.isEven"></div></f:if>
</f:if>
</f:for>
<f:if condition="{files->f:count()} == 3"></div></f:if>
</f:else>
</f:if>
</f:if>
<f:section name="item">
<div class="ce-textpic-file-download-wrapper">
<a href="/fileadmin/{file.identifier}" target="_blank">
<div class="filesize">({file.originalFile})</div>
</a>
</div>
</f:section>
EDIT2:
<f:section name="item">
<div class="ce-textpic-file-download-wrapper">
<f:link.typolink parameter="{file.publicUrl}" target="_blank">
{file.identifier}
<span class="filesize"> ({file.size->f:format.bytes()})</span>
</a>
</div>
just remember: aside of the properties visible in a <f:debug title="file">{file}</f:debug> you have a lot of other properies, given by the getters you could see in the API
I'm trying to dynamically create a menu using basic logic, something like this.
List item
List item
List item
List item
List item
I made this code
<ul>
<div data-th-each="field, iter : ${fields}" data-th-remove="tag">
<div data-th-if="${field.text} != null" data-th-switch="${field.href}" data-th-remove="tag">
<li data-th-case="null" data-th-utext="${field.text}" >
<li data-th-case="*"><a data-th-href="${field.href}" data-th-utext="${field.text}" ></a>
</div>
<ul data-th-if="${field}" class="sub-menu">
<div data-th-each="prop, propIter : ${field.sub_items.sub_item.properties}" data-th-remove="tag">
<div data-th-if="${prop.text} != null" data-th-switch="${prop.href}" data-th-remove="tag">
<li data-th-case="null" data-th-utext="${prop.text}"></li>
<li data-th-case="*"><a data-th-href="${prop.href}" data-th-utext="${prop.text}"></a></li>
</div>
</div>
</ul>
</li>
</div>
</ul>
But it returns parsing errors, I think it's mostly a Thymeleaf/HTML problem.
It's probably because of the unclosed "li" tags in the switch statement but I'm not sure how to fix it.
Right, it has to be valid html before processing. You can't do any kind of tricks like you have above, even if the output would be valid html.
I think you should be able to restructure your html to look like this:
<ul>
<th:block data-th-each="field, iter : ${fields}" data-th-if="${field.text} != null">
<li>
<span data-th-if="${field.href == null}" data-th-utext="${field.text}" />
<a data-th-unless="${field.href == null}" data-th-href="${field.href}" data-th-utext="${field.text}" />
<ul data-th-if="${field}" class="sub-menu">
<th:block data-th-each="prop, propIter : ${field.sub_items.sub_item.properties}" data-th-if="${prop.text} != null">
<span data-th-if="${prop.href == null}" data-th-utext="${prop.text}" />
<a data-th-unless="${prop.href == null}" data-th-href="${prop.href}" data-th-utext="${prop.text}" />
</th:block>
</ul>
</li>
</th:block>
</ul>
I've never though about using data-th-remove="tag" like you have. But I think you should be using <th:block> instead for cases like this.
We've got the following code:
<div th:each="client : ${clients}">
<div th:each="purchase : ${client.purchases}">
<div th:each="product : ${purchase.products}">
<span th:text="${product.price}"></span>
<!-- <span id="2" th:text="${#aggregates.sum(products.{price})}"> </span> -->
<!-- <span id="2" th:text="${#aggregates.sum(products.![price])}"> </span> -->
</div>
</div>
</div>
The output is:
5.25
4.20
If I uncomment the first comment, I get error
Exception evaluating SpringEL expression: "#aggregates.sum(products.{price})" (clients/clients:84)
If I uncomment only the second comment, I get error:
Exception evaluating SpringEL expression: "#aggregates.sum(products.![price])" (clients/clients:85)
I tried to use http://demo-dspace-cris.cineca.it/bitstream/123456789/26/1/usingthymeleaf.pdf
I am using thymeleaf 2.1.4
IT WORKS !
I should use:
<span id="2" th:text="${#aggregates.sum(purchase.products.![price])}"> </span>
I figured it myself after posting here !
<div th:each="client : ${clients}">
<div th:each="purchase : ${client.purchases}">
<span id="2"
th:text="${#aggregates.sum(purchase.products.![price])}"> </span>
<div th:each="product : ${purchase.products}">
<span th:text="${product.price}"></span>
</div>
</div>
</div>
output:
9.45
5.25
4.20
site that helped me a lot: http://forum.thymeleaf.org/aggregates-with-Spring-td3767446.html