In a Thymeleaf template I have two fragments:
<p th:fragment="paragraph(text)" style="padding: 20px; background: #eee;">[(${text})]</p>
<a th:fragment="link(href, text)" th:href="${href}">[[${text}]]</a>
Using these fragments on their own everything works as expected:
<p th:replace="~{ :: paragraph('Click here to get beauty: ')}" />
<a th:replace="~{ :: link('#', 'beauty ahead')}" />
But when I try to feed the link
fragment to the paragraph
fragment like this:
<!-- Doesn't work as expected -->
<p th:replace="~{ :: paragraph(~{ :: link('#', 'beauty ahead')})}" />
The HTML source code result is like that:
<p style="padding: 20px; background: #eee;">
<a th:fragment="link(href, text)" th:href="${href}">
<th:blockth:text="${text}"></th:block>
</a>
</p>
Although the link
fragment gets inserted, it doesn't get processed as expected.
Does anybody know how to tell Thymeleaf to process fragments correctly in nested layers?
Regarding to RoToRa's solution below I put his code here for further examination:
<a th:fragment="link(href, text)" th:href="${href}">[[${text}]]</a>
<p th:fragment="paragraph(content)" style="color: red">
<th:block th:replace="${content}"></th:block>
</p>
<p th:replace="~{ :: paragraph(~{ :: my-link}, '')}">
<a th:ref="my-link" th:replace="~{:: link('#', 'beauty ahead')}"></a>
</p>
The complete code is located in one and the same file, so targeting the paragraph fragment is adapted. Also I've changed <a th:ref="my-link" th:replace="~{link('#', 'beauty ahead')}></a>
to <a th:ref="my-link" th:replace="~{:: link('#', 'beauty ahead')}"></a>
since it seems that the link fragment wasn't referenced properly and the th:replace
needed to be closed.
Unfortunately, now the following error is thrown:
org.thymeleaf.exceptions.TemplateInputException: Error resolving fragment: "${content}": template or fragment could not be resolved
I'm not quite sure if it's my fault or if I'm still missing some point? Maybe RoToRa or someone else can nicely confirm that this code is really working?
It's working man, it's working:
fragments.html:
<!-- The fragments need to be loacated NOT in the same file where the replacement runs -->
<a th:fragment="link(href, text)" th:href="${href}">[[${text}]]</a>
<p th:fragment="paragraph(content)" style="color: red">
<th:block th:replace="${content}"></th:block>
</p>
layout.html:
<p th:replace="~{fragments :: paragraph(~{ :: my-link})}">
<a th:ref="my-link" th:replace="~{fragments :: link('#', 'beauty ahead')}"></a>
</p>
Many thanks to RoToRa for a great explanation and provided code!
Unfortunately Thymeleafs syntax isn't very nice for this use case.
The problem is that [(${text})]
(or [[${text}]]
) can only output text (a string), but you are passing in a fragment and fragments can only be output using th:replace
(or th:insert
). Also you'll need to reference an element that uses the "inner" fragment, instead of the "inner" fragment itself.
So the paragraph fragment needs to look like this:
<p th:fragment="paragraph(content)" style="color: red">
<th:block th:replace="${content}"></th:block>
</p>
and it is included like this:
<p th:replace="~{fragments::paragraph(~{::my-link})}">
<a th:ref="my-link" th:replace="~{link('#', 'beauty ahead')}></a>
</p>
Notice that the fragment expression ~{::my-link}
has to reference that specific element. You don't have to use th:ref
, but it's the most reliable. But don't reuse the name my-link
inside the same file, or that fragment expression will reference all instances. For example:
<p th:replace="~{fragments::paragraph(~{::my-link})}">
<a th:ref="my-link" th:replace="~{link('#', 'link1')}></a>
</p>
<p th:replace="~{fragments::paragraph(~{::my-link})}">
<a th:ref="my-link" th:replace="~{link('#', 'link2')}></a>
</p>
Will output (simplified):
<p><a href="#">link1</a><a href="#">link2</a></p>
<p><a href="#">link1</a><a href="#">link2</a></p>
Also this makes outputing simple text more complicated. You'll need to use a fragment expression in those cases, too:
<p th:replace="~{fragments::paragraph(~{::my-text})}">
<th:block th:ref="my-text">Click here to get beauty: </th:block>
</p>