54

I have created fragments.html file. It contains the following fragment:

<div th:fragment="my_fragment(content)"> <p th:text="${content}"></p> </div> 

I put the above fragment into my view file:

<div th:replace="fragments :: my_fragment('test')"></div> 

Now, I want to pass two parameters to my_fragment, but I must ensure backward compatibility.

I tried to solve the problem as follows:

<div th:fragment="my_fragment(content, defaultParameter='default value')"> <p th:text="${content}"></p> </div> 

Unfortunelly, the above solution generated error:

org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.thymeleaf.exceptions.TemplateProcessingException: Cannot resolve fragment. Signature "my_fragment (content,defaultParameter='default value')" declares 2 parameters, but fragment selection specifies 1 parameters. Fragment selection does not correctly match.

Any idea?

6 Answers 6

68

Thymeleaf allows a signature of a fragment without explicit parameters like this:

<div th:fragment="my_fragment"> <p th:text="${content}"></p> <p th:text="${defaultParameter}"></p> </div> 

To call this fragment and pass content and defaultParameter you may call the fragment as follows:

<!-- both parameters not specified --> <div th:replace="fragments :: my_fragment"></div> <!-- only content parameter specified --> <div th:replace="fragments :: my_fragment(content='a')"></div> <!-- both parameters specified --> <div th:replace="fragments :: my_fragment(content='a', defaultParameter='b')"></div> 

But the below will not work:

<div th:replace="fragments :: my_fragment('a', 'b')"></div> 

And the message is self-explanatory:

 Signature "my_fragment" declares no parameters, but fragment selection did specify parameters in a synthetic manner (without names), which is not correct due to the fact parameters cannot be assigned names unless signature specifies these names. 

So in case you want to maintain backward compatibility, you should use named parameters while calling a fragment and do not specify parameters in a signature of a fragment.

Sign up to request clarification or add additional context in comments.

5 Comments

Is it possible to set default value for the <p th:text="${defaultParameter}"></p> so it doesn't come up empty if parameter is not entered?
@perak yes it is by using th:with inside the fragment declaration tag. I have an example provided in my answer.
What one mustn't forget when passing variables, rather than literals, is to wrap those in ${} e.g. <th:block th:replace="common::buttons(fooBar = ${bar})" />
Is there any syntax to have it work with message resources? I tried the below in several styles all failing. <th:block th:replace="fragments/core00Frag::header(title='[[#{title.index}]]')"/>
try without quote, <th:block th:replace="fragments/core00Frag::header(title=#{title.index})"/>
48

The best way to allow optional parameter for a fragment is to declare them with "th:with" and describe them with meaningful default values.

So, you define explicit your mandatory and your optional values in the declaration tag of your fragment.

Here is simple example, with 1 mandatory and 2 optional parameters:

<div th:fragment="printGreetings (name)" th:with="title=${title} ?: 'Mr.', greeting=${greeting} ?: 'Hello'"> <span th:text="${greeting}">Hello</span> <span th:text="${title}">Mr.</span> <span th:text="${name}">John Doe</span> </div> 

You can then call it like the following:

<div th:replace="fragments :: printGreetings (name='daniel')"> Hello Mr. Daniel </div> <div th:replace="fragments :: printGreetings (name='Lea', title='Ms.')> Hello Ms. Lea </div> <div th:replace="fragments :: printGreetings (name='Lea', title='Ms.', greeting='Hi')> Hi Ms. Lea </div> 

(Please note that all values inside the tags are replaced by the dynamic ones. It's just for better reading.)

1 Comment

Much cleaner, and solves the actual problem of the original question
8

If you want to provide a default value (as in your case) you can use the elvis operator to specify a default value, for example:

<div th:fragment="my_fragment"> <p th:text="${content}"></p> <p th:text="${defaultParameter ?: 'the backwards compat value'}"></p> </div> 

see: elvis-operator

Comments

3

At last I've solved task like this:

<div th:fragment="my_fragment2(content, param2)"> <th:block th:replace="my_fragment(${content})"/> </div> <div th:fragment="my_fragment(content)" th:with="param2=${param2} ?: 'default value'"> <p th:text="${content}"></p> <p th:text="${param2}"></p> </div> <div th:replace="fragments :: my_fragment2('test', 'my_value')"></div> 

But it is overhead:(

Also ask a question to issue Allow fragment parameters to have default values

Comments

1

The best way for me :

Fragment:

<div th:fragment="my_fragment(content)"> <p th:text="${content}"></p> </div> 

if content is given from model:

<div th:replace="fragments :: my_fragment(${content})></div>

else:

<div th:replace="fragments :: my_fragment('content')></div>

Comments

0

Sample for bootstrap's form's fiels with default maxlength and type

<th:block th:fragment="field(name,label,required)" th:with="type=${type} ?:'text', maxlength=${maxlength} ?:32"> <div class="mb-3 row"> <div class="col-md-2"> <label th:for="${name}" class="col-form-label" th:text="${label}"/> </div> <div class="col-md-5 col-10"> <input th:type="${type}" th:maxlength="${maxlength}" class="form-control" th:id="${name}" th:field="*{__${name}__}" th:errorclass="is-invalid" th:classappend="${saved} ? 'is-valid':''" th:required="${required}"> <div class="invalid-feedback" th:errors="*{__${name}__}"></div> </div> </div> </th:block> 

Usage:

<th:block th:insert="~{layout/fragments::field('email','E-mail',true)}"/> <th:block th:insert="~{layout/fragments::field('firstName','First Name',true)}"/> <th:block th:insert="~{layout/fragments::field('lastName','Last Name',false)}"/> <th:block th:insert="~{layout/fragments::field('url','Site URL',true)}" th:with="maxlength=128, type='url'"/> <th:block th:insert="~{layout/fragments::field('inn','INN',false)}" th:with="maxlength=12, type='number'"/> 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.