Tutorial: Thymeleaf + Spring
Tutorial: Thymeleaf + Spring
Thymeleaf
Preface
This tutorial explains how Thymeleaf can be integrated with the Spring Framework, especially (but not only) Spring
MVC.
Note that Thymeleaf has integrations for versions 3.x and 4.x of the Spring Framework, provided by two libraries.
separate callsthymeleaf-spring3ethymeleaf-spring4These libraries are packaged in.jarfiles
separated (thymeleaf-spring3-{version}.jare thymeleaf-spring4-{version}.jar) and need to be added to your
class path to use Thymeleaf Spring integrations in your application.
The code samples and the application example in this tutorial use Spring 4.x and its Thymeleaf integrations.
correspondents, but the content of this text is also valid for Spring 3.x. If your application uses Spring 3.x, all of the
what you need to do is replace theorg.thymeleaf.spring4packageorg.thymeleaf.spring3in the code examples.
Make the methods mapped in theControllerobjects of Spring MVC are forwarded to the models
managed by Thymeleaf, just like you do with JSPs.
Use Spring Expression Language (Spring EL) instead of OGNL in your templates.
Create forms in your models fully integrated with your form backup beans and links.
results, including the use of property editors, conversion services, and validation error handling.
Display internationalization messages from message files managed by Spring (through the
MessageSourceusual objects.
Solve your models using the resource resolution mechanisms of Spring.
Note that in order to fully understand this tutorial, you must first have read the tutorial 'Using Thymeleaf'.
Explain the Standard Dialect in depth.
This specific dialect is based on the Thymeleaf Standard Dialect and is implemented in a class called
org.thymeleaf.spring4.dialect.SpringStandardDialect, which actually extends from
org.thymeleaf.standard.StandardDialect.
In addition to all the features already present in the Standard Dialect - and therefore inherited - the SpringStandard Dialect presents the
specific resources:
Use Spring Expression Language (Spring EL or SpEL) as a variable expression language, instead of OGNL.
Consequently, all expressions${...} e*{...} will be evaluated by the expressions language mechanism
from Spring. Note also that support for the Spring EL compiler is available (Spring 4.2.4+).
Access any beans in the application context using SpringEL syntax:${@myBean.doSomething()}
New attributes for form processing:th:field, th:errorsanderrorclass, in addition to a new
implementation of theth:objectwhich allows it to be used for form command selection.
An object and method of expression#themes.code(...), which is equivalent tospring:themecustom JSP tag.
An object and method of expression#mvc.uri(...), which is equivalent tospring:mvcUrl(...)custom JSP function
(only on Spring 4.1+).
Note that most of the time you should not use this dialect directly in aTemplate Enginenormal object like
part of its configuration. Unless you have very specific Spring integration needs, you should be a
creation of an instance of a new model engine class that executes all the necessary configuration steps
automaticallyorg.thymeleaf.spring4.SpringTemplateEngine.
@Bean
publicSpringResourceTemplateResolver templateResolver(){
SpringResourceTemplateResolver automatically integrates with Spring's own
resource resolution infrastructure, which is highly recommended.
SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
templateResolver.setApplicationContext(this.applicationContext);
templateResolver.setPrefix("/WEB-INF/templates/");
templateResolver.setSuffix(".html");
// HTML is the default value, added here for the sake of clarity.
templateResolver.setTemplateMode(TemplateMode.HTML);
// Template cache is true by default. Set to false if you want
// templates to be automatically updated when modified.
templateResolver.setCacheable(true);
returntemplateResolver;
}
@Bean
publicSpringTemplateEngine templateEngine()
// SpringTemplateEngine automatically applies SpringStandardDialect and
Enables Spring's own MessageSource message resolution mechanisms.
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver());
Enabling the SpringEL compiler with Spring 4.2.4 or newer can
// speed up execution in most scenarios, but might be incompatible
with specific cases when expressions in one template are reused
// across different data types, so this flag is "false" by default
// for safer backwards compatibility.
templateEngine.setEnableSpringELCompiler(true);
returntemplateEngine;
}
Unable to access the content of the provided URL for translation. 5/34
01/08/2020 Tutorial: Thymeleaf + Spring
There are two interfaces in Spring MVC that comply with the core of its model system:
org.springframework.web.servlet.View
org.springframework.web.servlet.ViewResolver
Visualize the template pages in our applications and allows us to modify and predict their behavior by defining them.
like beans. The views are responsible for rendering the actual HTML interface, usually by executing some
model mechanism like Thymeleaf.
ViewResolvers are the objects responsible for obtaining View objects for a specific operation and location.
Normally, controllers ask the ViewResolvers to forward to a view with a specific name (a
String returned by the controller method) and then all view resolvers in the application are executed
in an ordered chain until one of them can resolve this display, in which case a View object is returned and the control
be passed to the HTML rendering.
Note that not all pages in our applications need to be defined as Views, but only those whose
We desire behavior that is not standard or configured in a specific way (for example, connecting
some special beans to it). If a ViewResolver is requested for a view that does not have a bean.
correspondent, which is the common case, a new View object will be created ad hoc and returned.
A typical configuration for a JSP + JSTL ViewResolver in a Spring MVC application from the past was like this:
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
viewClass
prefix
suffix
order
viewNames
</bean>
viewClassestablishes the class of display instances. This is necessary for a JSP resolver, but will not be
necessary when we are working with Thymeleaf.
prefixandsuffixwork similarly to the attributes with the same names in the TemplateResolver objects
Thymeleaf.
orderestablishes the order in which the ViewResolver will be consulted in the chain.
viewNamesallows the definition (with wildcard characters) of the display names that will be resolved by this
ViewResolver.
org.thymeleaf.spring4.view.ThymeleafView
org.thymeleaf.spring4.view.ThymeleafViewResolver
These two classes will be responsible for processing the Thymeleaf models as a result of the execution of the
controllers.
@Bean
publicThymeleafViewResolver viewResolver() {
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setTemplateEngine(templateEngine());
// NOTE 'order' and 'viewNames' are optional
viewResolver.setOrder(1);
viewResolver.setViewNames(new String[] {".html", ".xhtml"});
returnviewResolver;
}
... Or in XML:
<bean class="org.thymeleaf.spring4.view.ThymeleafViewResolver">
templateEngine
NOTE 'order' and 'viewNames' are optional
1
viewNames
</bean>
ThetemplateEnginethe parameter is, obviously, theSpringTemplateEngineobject that we defined in the previous chapter. The others
twoorderandviewNames) are optional and have the same meaning as in the JSP ViewResolver we saw earlier.
Note that we don't needprefixorsuffixparameters, because they are already specified in the Model Resolver
(which in turn is passed to the Model Mechanism).
And if we wanted to define aViewCome and add some static variables? Easy, just define a prototype of bean to
he:
@Bean
@Scope("prototype")
publicThymeleafView mainView() {
ThymeleafView view = new ThymeleafView("main"); // templateName = 'main'
view.setStaticVariables(
The ACME Fruit Company
returnview;
}
By doing this, you will be able to run specifically this display bean, selecting it by the name of the bean.mainView,
in this case)
At Thymeleaf, we are big fans of thyme, and every spring we prepare our seed starter kits with good soil and
Our favorite seeds, we placed under the Spanish sun and patiently wait for our new plants to grow.
But this year we got tired of sticking labels on the beginner containers to know which seed was in each cell of
container, so we decided to prepare an application using Spring MVC and Thymeleaf to help us catalog our
beginners: The Spring Thyme SeedStarter Manager.
In a similar way to the Good Thymes Virtual Grocery app that we developed in the tutorial Using Thymeleaf, the STSM
will allow us to exemplify the most important aspects of integrating Thymeleaf as a templating engine for the
Spring MVC.
We will need a very simple business layer for our application. First of all, let's take a look at
our model entities:
STSM Model
Some very simple service classes will provide the necessary business methods. Like:
Service
public class SeedStarterService {
@Autowired
privateSeedStarterRepository seedstarterRepository;
@Service
public classVarietyService {
@Autowired
privateVarietyRepository varietyRepository;
}
Invalid input. Please provide text to translate. 9/34
01/08/2020 Tutorial: Thymeleaf + Spring
Next, we need to define the Spring MVC configuration for the application, which will include not only the standard artifacts
from Spring MVC, such as resource handling or annotation scanning, but also the creation of Template instances
Engine and the View Resolver.
@Configuration
Enable Web MVC
Component Scan
public class SpringWebConfig
extends WebMvcConfigurerAdapter implements ApplicationContextAware {
privateApplicationContext applicationContext;
publicSpringWebConfig() {
super();
}
/* ******************************************************************* */
GENERAL CONFIGURATION ARTIFACTS */
Static Resources, i18n Messages, Formatters (Conversion Service) */
/* ******************************************************************* */
@Override
public void addResourceHandlers(final ResourceHandlerRegistry registry) {
super.addResourceHandlers(registry);
registry.addResourceHandler("/images/**").addResourceLocations("/images/");
registry.addResourceHandler("/css/**").addResourceLocations("/css/");
registry.addResourceHandler("/js/**").addResourceLocations("/js/");
}
@Bean
publicResourceBundleMessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setBasename("Messages");
returnmessageSource;
}
@Override
public void addFormatters(final FormatterRegistry registry) {
super.addFormatters(registry);
registry.addFormatter(varietyFormatter());
registry.addFormatter(dateFormatter());
}
@Bean
publicVarietyFormatter varietyFormatter() {
return newVarietyFormatter();
}
@Bean
publicDateFormatter dateFormatter() {
return newDateFormatter();
}
/* **************************************************************** */
/* THYMELEAF-SPECIFIC ARTIFACTS */
/* TemplateResolver <- TemplateEngine <- ViewResolver */
/* **************************************************************** */
@Bean
publicSpringResourceTemplateResolver templateResolver() {
SpringResourceTemplateResolver automatically integrates with Spring's own
resource resolution infrastructure, which is highly recommended.
SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
templateResolver.setApplicationContext(this.applicationContext);
templateResolver.setPrefix("/WEB-INF/templates/");
templateResolver.setSuffix(".html");
HTML is the default value, added here for the sake of clarity.
templateResolver.setTemplateMode(TemplateMode.HTML);
// Template cache is true by default. Set to false if you want
// templates to be automatically updated when modified.
templateResolver.setCacheable(true);
returntemplateResolver;
}
@Bean
public SpringTemplateEngine templateEngine() {
// SpringTemplateEngine automatically applies SpringStandardDialect and
// enables Spring's own MessageSource message resolution mechanisms.
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver());
Enabling the SpringEL compiler with Spring 4.2.4 or newer can
// Speed up execution in most scenarios, but might be incompatible
with specific cases when expressions in one template are reused
across different data types, so this flag is "false" by default
// for safer backwards compatibility.
templateEngine.setEnableSpringELCompiler(true);
returntemplateEngine;
}
@Bean
publicThymeleafViewResolver viewResolver() {
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setTemplateEngine(templateEngine());
returnviewResolver;
}
Obviously, we will also need a controller for our application. Since the STSM will contain only one page of
web with a list of beginners and a form to add new ones, we will write only one controller class for
all server interactions:
@Controller
public class SeedStarterMngController {
@Autowired
privateVarietyService varietyService;
@Autowired
privateSeedStarterService seedStarterService;
...
}
https://www.thymeleaf.org/doc/tutorials/3.0/thymeleafspring.html 11/34
01/08/2020 Tutorial: Thymeleaf + Spring
Model attributes
First, we will add some model attributes that we will need on the page:
allTypes
public List<Type> populateTypes() {
returnArrays.asList(Type.ALL);
}
@ModelAttribute("allFeatures")
public List<Feature> populateFeatures() {
returnArrays.asList(Feature.ALL);
}
@ModelAttribute("allVarieties")
publicList<Variety> populateVarieties() {
return this.varietyService.findAll();
}
@ModelAttribute("allSeedStarters")
publicList<SeedStarter> populateSeedStarters() {
return this.seedStarterService.findAll();
}
Mapped methods
And now the most important part of a controller, the mapped methods: one to show the form page and another
to process the addition of newSeedStarterobjects.
/, /seedstartermng
public String showSeedstarters(final SeedStarter seedStarter) {
seedStarter.setDatePlanted(Calendar.getInstance().getTime());
seedstartermng
}
@RequestMapping(value="/seedstartermng", params={"save"})
publicString saveSeedstarter(
finalSeedStarter seedStarter, finalBindingResult bindingResult, finalModelMap model) {
if(bindingResult.hasErrors()) {
return "seedstartermng";
}
this.seedStarterService.add(seedStarter);
model.clear();
return"redirect:/seedstartermng";
}
To allow easy formattingDateand also of theVarietyobjects in our visualization layer, we configured our
app for aConversionServicethe Spring object would be created and initialized (as per
WebMvcConfigurerAdapterWe extend) with some formatting objects that we will need. See again:
@Override
public void addFormatters(final FormatterRegistry registry) {
super.addFormatters(registry);
registry.addFormatter(varietyFormatter());
Unable to access the specified URL. Please provide the text to be translated. 12/34
01/08/2020 Tutorial: Thymeleaf + Spring
registry.addFormatter(dateFormatter());
}
@Bean
publicVarietyFormatter varietyFormatter() {
return newVarietyFormatter();
}
@Bean
publicDateFormatter dateFormatter() {
return newDateFormatter();
}
Let's take a look atDateFormatter, which formats dates according to a format string present in the
date.formatmessage key of ourMessages.properties:
@Autowired
privateMessageSource messageSource;
publicDateFormatter() {
super();
}
OVarietyFormatterautomatically convert between ourVarietyentities and the way we want to use them in
our forms (basically, by theidfield values:
@Autowired
privateVarietyService varietyService;
publicVarietyFormatter() {
super();
}
We will learn more about how these formatters affect the way our data is displayed later.
<table>
<thead>
<tr>
Date Planted
Covered
Type
Features
Rows
<trth:each="sb : ${allSeedStarters}">
13/01/2011
yes
Wireframe
#messages.arrayMsg(
#strings.arrayPrepend(sb.features,'seedstarter.feature.')),
Electric Heating, Turf
<td>
<table>
<trth:each="row,rowStat : ${sb.rows}">
1
Thyme Thyme
12
</tr>
</tr>
There's much to see here. Let's take a look at each fragment separately.
First of all, this section will only be shown if there is any initial match. We achieve this with a th: unless
attribute and the#lists.isEmpty(...)function.
Note that all utility objects such as#listsare available in Spring EL expressions in the same way as
they were in the OGNL expressions in the standard Dialect.
The next thing to see is many internationalized (externalized) texts, such as:
<table>
<thead>
@Bean
publicResourceBundleMessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setBasename("Messages");
returnmessageSource;
}
... And thisbasenameproperty indicates that we will have files such asMessages_es.properties orMessages_en.propertiesin
our class path. Let's take a look at the Spanish version:
Seedbed List
dd/MM/yyyy
yes
no
Planting date
Covered
Type
Features
Rows
Wood
Plastic
In the first column of the table list, we will show the date when the seed initiator was prepared. But we
we will show formatted in the way we define in ourDateFormatterTo do this, we will use bracket syntax
duplo${{...}} ), which will automatically apply the Spring Conversion Service, including theDateFormatterwhat we register
in the configuration.
13/01/2011
Next, we show whether the seed starter container is covered or not, transforming the value of the property.
boolean of the covered bean in an internationalized "yes" or "no" with a literal replacement expression:
yes
Now we have to show the type of seed starter container. Type is a Java enumeration with two values (
WOODePLASTIC), and that is why we define two properties in ourMessagesfile called
seedstarter.type.WOODandPLASTIC.
But, to obtain the internationalized names of the types, we will need to add theseedstarter.type.prexo to the value of
enumeration through an expression, the result of which we will use as the message key:
Wireframe
Note that this is particularly difficult because these enum values also need to be externalized, such as zemos
with Types. The flow is then:
<tdth:text="${#strings.arrayJoin(
Invalid input
#strings.arrayPrepend(sb.features,'seedstarter.feature.')
Electric Heating, Turf
The last column of our listing will be quite simple. Even if there is a nested table to show the content of
each line in the container:
<td>
<table>
<tbody>
<trth:each="row,rowStat : ${sb.rows}">
1
Thymus Thymi
12
</tr>
</tbody>
</table>
</td>
6 Creating a form
Command object is the name that Spring MVC assigns to form backup beans, that is, to the objects that model
the fields of a form and provide getter and setter methods that will be used by the framework to establish and obtain
the values entered by the user on the client side.
Thymeleaf requires you to specify the command object using ath:objectattribute in your<form>tag
#
...
This is consistent with other uses,th:object,but, in fact, this specific scenario adds some limitations to
correctly integrate into the Spring MVC infrastructure:
The values forth:objectattributes in form tags must be variable expressions (${...}) specifying
just the name of a model attribute, without property navigation. This means that an expression like
${seedStarter}it is valid, but${seedStarter.data}it wouldn't be.
Once inside the<form>tag, no otherth:objectattribute can be specified. This is consistent with the fact that
that HTML forms cannot be nested.
6.2 Entries
As you can see, we are introducing a new attribute here:th:field.This is a very important resource for the
Spring MVC integration because it does all the heavy lifting of binding your input to a property on the backing bean
of the form. You can see it as an equivalent of the path attribute in a tag from the Spring MVC tag library.
<inputtype="text"id="datePlanted"name="datePlanted"th:value="*{datePlanted}" />
... But, in fact, it's a little more than that, becauseth:fieldwill also apply the spring conversion service
registered, including theDateFormatterwhat we saw earlier (even if the field expression is not among
double brackets). Thanks to this, the date will be displayed correctly formatted.
The values forth:fieldattributes should be selection expressions (*{...}), which makes sense, as they will be evaluated
no backup bean of form and not in the context variables (or model attributes in Spring MVC jargon).
Contrary to what is insideth:objectthese expressions may include property navigation (in fact, any
allowed expression for the path attribute of a<form:input>JSP tag will be allowed here).
It should be noted thatth:fieldalso includes the new types of<input>element introduced by HTML5 as<input
type="datetime" ... />, <input type="color" ... />etc., effectively adding full HTML5 support for
Spring MVC.
The content at the provided URL is not accessible for translation. 18/34
01/08/2020 Tutorial: Thymeleaf + Spring
Model attributes
First, we will add some model attributes that we will need on the page:
allTypes
public List<Type> populateTypes() {
returnArrays.asList(Type.ALL);
}
@ModelAttribute("allFeatures")
public List<Feature> populateFeatures() {
returnArrays.asList(Feature.ALL);
}
@ModelAttribute("allVarieties")
publicList<Variety> populateVarieties() {
return this.varietyService.findAll();
}
@ModelAttribute("allSeedStarters")
publicList<SeedStarter> populateSeedStarters() {
return this.seedStarterService.findAll();
}
Mapped methods
And now the most important part of a controller, the mapped methods: one to show the form page and another
to process the addition of newSeedStarterobjects.
/, /seedstartermng
public String showSeedstarters(final SeedStarter seedStarter) {
seedStarter.setDatePlanted(Calendar.getInstance().getTime());
seedstartermng
}
@RequestMapping(value="/seedstartermng", params={"save"})
publicString saveSeedstarter(
finalSeedStarter seedStarter, finalBindingResult bindingResult, finalModelMap model) {
if(bindingResult.hasErrors()) {
return "seedstartermng";
}
this.seedStarterService.add(seedStarter);
model.clear();
return"redirect:/seedstartermng";
}
To allow easy formattingDateand also of theVarietyobjects in our visualization layer, we configured our
app for aConversionServicethe Spring object would be created and initialized (as per
WebMvcConfigurerAdapterWe extend) with some formatting objects that we will need. See again:
@Override
public void addFormatters(final FormatterRegistry registry) {
super.addFormatters(registry);
registry.addFormatter(varietyFormatter());
Unable to access the specified URL. Please provide the text to be translated. 12/34
01/08/2020 Tutorial: Thymeleaf + Spring
correspondents.
The radio button fields are specified similarly to non-boolean checkboxes (with multiple
values) - except that they don't have multiple values, of course:
<ul>
<lith:each="ty : ${allTypes}">
<input type="radio" th:field="*{type}" th:value="${ty}" />
Wireframe
The selection fields have two parts: the<select>brand and its<option>nested tags. When creating this type of field,
just a<select>tag needs to include ath:fieldattribute, but theth:valueattributes in the<option>nested tags will be
very important because they will provide the means to know what the currently selected option is (similarly to
non-boolean checkboxes and radio buttons.
<select th:field="*{type}">
<option th:each="type : ${allTypes}"
th:value="${type}"
Wireframe
At this point, understanding this piece of code is quite easy. Note how the attribute precedence allows us to define the
th:eachattribute no<option>own tag.
Thanks to the advanced form field binding features in Spring MVC, we can use complex expressions from
Spring EL to bind dynamic form fields to our form backup bean. This will allow us to create
new Row objects in ourSeedStartercome and add the fields of these lines to our form,
user request.
To do this, we will need some new methods mapped in our controller, which will add or remove a
line of ours,SeedStarterdepending on the existence of specific request parameters:
@RequestMapping(value="/seedstartermng", params={"addRow"})
public String addRow(final SeedStarter seedStarter, final BindingResult bindingResult) {
seedStarter.getRows().add(newRow());
seedstartermng
}
@RequestMapping(value="/seedstartermng", params={"removeRow"})
publicString removeRow(
finalSeedStarter seedStarter, finalBindingResult bindingResult,
finalHttpServletRequest req) {
finalInteger rowId = Integer.valueOf(req.getParameter("removeRow"));
seedStarter.getRows().remove(rowId.intValue());
seedstartermng
}
<table>
<thead>
<tr>
Row
Variety
Seeds per cell
<th>
Add row
</th>
<tbody>
<trth:each="row,rowStat : *{rows}">
1
<select th:field="*{rows[__${rowStat.index}__].variety}">
<option th:each="var : ${allVarieties}"
th:value="${var.id}"
Thymus Thymi
</select>
</td>
<td>
<input type="text" th:field="*{rows[__${rowStat.index}__].seedsPerCell}" />
</td>
<td>
<button type="submit" name="removeRow">
Remove row
Many things to see here, but not much that we shouldn't understand so far... except for one.strangething
<selectth:field="*{rows[__${rowStat.index}__].variety}">
...
If you remember the tutorial 'Using Thymeleaf', this__${...}__syntax is a pre-processing expression, which is
an internal expression that is evaluated before actually evaluating the whole expression. But why this way of specifying the
line index? Wouldn't it be enough with:
<select th:field="*{rows[rowStat.index].variety}">
...
... well, actually no. The problem is that Primavera EL does not evaluate variables within array index braces, so
when executing the above expression, we would get an error telling us thatrows[rowStat.index](instead ofrows[0], rows[1], etc)
it is not a valid position in the row collection. That's why preprocessing is necessary here.
Let's take a look at a fragment of the resulting HTML after pressing 'Add row' a few times:
<tr>
1
<td>
<td>
<inputid="rows0.seedsPerCell"name="rows[0].seedsPerCell"type="text"value="" />
</td>
<td>
<buttonname="removeRow"type="submit"value="0">Remove row</button>
</td>
<tr>
2
<td>
<select id="rows1.variety" name="rows[1].variety">
<optionselected="selected"value="1">Thymus vulgaris</option>
Thymus x citriodorus
Thymus herba-barona
Thymus pseudolaginosus
Thyme
</select>
</td>
<td>
<inputid="rows1.seedsPerCell"name="rows[1].seedsPerCell"type="text"value="" />
</td>
<td>
<buttonname="removeRow"type="submit"value="1">Remove row</button>
</td>
</tr>
</tbody>
The provided link is a URL and does not contain translatable text. 22/34
01/08/2020 Tutorial: Thymeleaf + Spring
Thymeleaf offers some tools for this: some functions in#fieldsthe objectth:errorsand the
Error Classattributes.
Let's see how we can define a specific CSS class for a field, if there is an error:
As you can see, the#fields.hasErrors(...)function receives the field expression as a parameter (date planted) e
returns a boolean indicating whether there are validation errors for this field.
We can also get all the errors from this field and iterate through them:
<ul>
<lith:each="err : ${#fields.errors('datePlanted')}"th:text="${err}" />
Instead of iterating, we could also useerrorsa specialized attribute that creates a list of all the errors of the
specified selector, separated by<br />:
The example we saw above, defining a CSS class for a form input if that field has errors, is so common that
Thymeleaf offers a specific attribute to do exactly that:errorclass.
Applied to a form field tag (input, select, textarea ...), it will read the name of the field to be examined.
based on any existing onenameorth:fieldattributes in the same tag, and then add the specified CSS class
for the tag if such field has any associated errors:
And if we want to show all the errors on the form? We just need to check the#fields.hasErrors(...)e
fields.errors(...)methods with the'*'orallconstants (that are equivalent):
if="${#fields.hasErrors('*')}"
Input is incorrect
As in the examples above, we can obtain all the errors and iterate through them...
<ul>
<lith:each="err : ${#fields.errors('*')}" th:text="${err}" />
Incorrect date
if(${#fields.hasAnyErrors()})
...<p th:each="err : ${#fields.allErrors()}"> <span th:text="${err}"></span></p>...
</div>
There is a third type of error in the Spring form: global errors. These are errors that are not associated with any field.
specific in the form, but they still exist.
if="${#fields.hasErrors('global')}"
Input is incorrect
</ul>
Incorrect date
<divth:if="${#fields.hasGlobalErrors()}">
...
translatedText
Form validation errors can also be displayed outside of forms using variable expressions.
${...}) instead of selection (*{...}and prefixing the backup form bean name:
<divth:errors="${myForm}">...</div>
${myForm.date}
<divth:errors="${myForm.*}">...</div>
Thymeleaf offers the possibility to obtain form error information in the form of beans (instead of mere strings
), with the attributesfieldName(String)messageeglobalboolean
<ul>
<lith:each="e : ${#fields.detailedErrors()}" th:class="${e.global}? globalerr : fielderr">
The field name
The error message
</li>
</ul>
One of the most pleasant consequences of working with Thymeleaf is that, after all this functionality that
we added to our HTML, we can still use it as a prototype (we say it is a Natural Model). Let's open
seedstartermng.htmldirectly in our browser without running our application:
Here it is! It's not a functional app, it's not real data... but it's a perfectly valid prototype, made up of code.
Perfectly displayable HTML.
Unable to access the content of the provided URL for translation. 26/34
August 1, 2020 Tutorial: Thymeleaf + Spring
9.1 Configuration
As explained earlier, Thymeleaf can use a Conversion Service registered in the Application Context. Our
application configuration class, extending the itselfWebMvcConfigurerAdapterSpring assistant will register
automatically this conversion service, which we can configure by adding the formatters we need.
Let's see again how it is:
@Override
public void addFormatters(final FormatterRegistry registry) {
super.addFormatters(registry);
registry.addFormatter(varietyFormatter());
registry.addFormatter(dateFormatter());
}
@Bean
publicVarietyFormatter varietyFormatter() {
return newVarietyFormatter();
}
@Bean
publicDateFormatter dateFormatter() {
return newDateFormatter();
}
The Conversion Service can be easily applied to convert/format any object into String. This is done by
the syntax of the expression between braces:
Thus, for example, given an integer-to-string converter that adds commas as thousands separators, this:
${val}
<pth:text="${{val}}">...</p>
1234567890
1,234,567,890
We saw earlier that eachth:fieldattribute will always apply the conversion service, therefore, this:
The provided link does not contain text that can be translated. 27/34
01/08/2020 Tutorial: Thymeleaf + Spring
Note that, by the requirement of Spring, this is the only scenario in which the Conversion Service is applied to expressions.
using single-key syntax.
9.3#conversionsutility object
O #conversions The utility expression object allows for the manual execution of the Conversion Service whenever necessary:
Val:
This can be a useful component tool. For example, it can be used in controllers run on
AJAX calls, which can return fragments of markup from a page that is already loaded in the browser (to
update selection buttons, activation/deactivation ...)
Fragment rendering can be achieved using Thymeleaf fragment specifications: objects implementing the
org.thymeleaf.fragment.IFragmentSpecinterface.
View beans are beans fromorg.thymeleaf.spring4.view.ThymeleafViewclass declared in the context of the application
Beandeclarations if you are using the Java configuration). They allow the specification of fragments like this:
@Bean(name="content-part")
@Scope("prototype")
publicThymeleafView someViewBean() {
ThymeleafView view = new ThymeleafView("index"); // templateName = 'index'
view.setMarkupSelector("content");
returnview;
}
Given the bean definition above, if our controller returnscontent-part(the name of the bean above)...
/showContentPart
public String showContentPart() {
...
return"content-part";
}
... Thymeleaf will return only thecontentfragment of theindexmodel - what place will probably be something like this/WEB-
INF/templates/index.htmlwhen the price and the taxes are applied. Therefore, the result will be completely
equivalent to specifyingcontent:
<!DOCTYPE html>
<html>
...
<body>
...
<divth:fragment="content">
Only this div will be rendered!
...
</body>
Also note that, thanks to the power of Thymeleaf markup selectors, we can select a fragment in a
model without needing anyth:fragmentattribute. Let's use theidattribute, for example:
@Bean(name="content-part")
@Scope("prototype")
public ThymeleafView someViewBean() {
ThymeleafView view = new ThymeleafView("index"); // templateName = 'index'
view.setMarkupSelector("#content");
return view;
}
<!DOCTYPE html>
<html>
...
<body>
...
...
</body>
Instead of declaring display beans, fragments can be specified within the controllers themselves using the syntax
the expressions of fragment. Just like ininsertorth:replaceattributes:
/showContentPart
public String showContentPart() {
...
index :: content
}
Obviously, again all the power of the DOM Selectors is available, so we can select our fragment.
based on standard HTML attributes, such asid="content":
/showContentPart
public String showContentPart() {
...
return"index :: #content";
}
/showContentPart
public String showContentPart() {
...
return "index :: #content ('myvalue')";
}
@Bean
publicRequestDataValueProcessor requestDataValueProcessor() {
return newMyRequestDataValueProcessor();
}
Note that there are very few scenarios in which you would need to explicitly implement.
RequestDataValueProcessorin your app. In most cases, this will be used automatically by the
security libraries that you use transparently, such as the CSRF support of Spring Security.
Since version 4.1, Spring allows the possibility of creating links to annotated controllers directly from the views,
without the need to know the URIs to which these controllers are mapped.
In Thymeleaf, this can be achieved through the#mvc.url(...)expression object method, which allows the
specification of controller methods by the uppercase letters of the controller class in which they are located, followed by
name of the own method. This is equivalent tospring:mvcUrl(...)custom function of JSP.
/data
public String getData(Model model) { ...return "template" }
/data
Unable to access content from the link provided. 31/34
01/08/2020 Tutorial: Thymeleaf + Spring
public String getDataParam(@RequestParam String type) { ...return "template" }
The Thymeleaf + Spring integration packages include integration with Spring WebFlow (2.3+).
WebFlow includes some AJAX features to render fragments of the displayed page when specific events occur.
transitions) are triggered and, to allow Thymeleaf to respond to these AJAX requests, we will have to use a
ViewResolverdifferent implementation, configured as follows:
<bean id="mvcViewFactoryCreator"
class="org.springframework.webflow.mvc.builder.MvcViewFactoryCreator"
viewResolvers
From here, you can specify Thymeleaf templates in your display states:
<view-stateid="detail"view="bookingDetail">
...
</view-state>
In the example above,booking detailit is a Thymeleaf model specified in the usual way, understandable by anyone
model resolvers configured inTemplate Engine.
Note that what is explained here is only the way to create AJAX fragments to be used with Spring.
WebFlow. If you are not using WebFlow, create a Spring MVC controller that responds to a request.
AJAX and return a piece of HTML is as simple as creating any other model return controller, with
the only exception being that you would probably return a fragment likemain :: adminof your method of
control.
WebFlow allows the specification of fragments to be rendered via AJAX withtranslatedTexttags, like this:
detail
<transition on="updateData">
<renderfragments="hoteldata"/>
These fragments (hotel datain this case) can be a comma-separated list of fragments specified in the markup
withth:fragment:
Always remember that the specified fragments must have aidattribute, for the Spring JavaScript libraries in
execution in the browser can replace the markup.
detail
updateData
[//div[@id='data']]
</view-state>
<scripttype="text/javascript"th:src="@{/resources/dojo/dojo.js}"></script>
<script type="text/javascript" th:src="@{/resources/spring/Spring.js}"></script>
<scripttype="text/javascript"th:src="@{/resources/spring/Spring-Dojo.js}"></script>
...
<formid="triggerform"method="post"action="">
<inputtype="submit"id="doUpdate"name="_eventId_updateData"value="Update now!" />
</form>
<scripttype="text/javascript">
Spring.addDecoration(
new Spring.AjaxEventDecoration({formId:'triggerform',elementId:'doUpdate',event:'onclick'}));