-
Notifications
You must be signed in to change notification settings - Fork 20
Expand file tree
/
Copy pathlayouts.html
More file actions
435 lines (415 loc) · 39.6 KB
/
layouts.html
File metadata and controls
435 lines (415 loc) · 39.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
<!DOCTYPE html>
<html>
<head>
<title>Thymeleaf Page Layouts - Thymeleaf</title>
<meta charset="UTF-8"/>
<meta name="generator" content="pandoc">
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<link rel="icon" href="../images/favicon.ico"/>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Ubuntu:400,400italic,700,700italic"/>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Ubuntu+Mono:400,700,400italic,700italic"/>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/2.1.3/normalize.min.css" media="screen"/>
<link rel="stylesheet" href="../styles/thymeleaf.css"/>
<link rel="stylesheet" href="../styles/thymeleaf-articles.css"/>
<script src="https://unpkg.com/dumb-query-selector@3.0.0/dumb-query-selector.js" defer></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.6.0/components/prism-core.min.js" defer data-manual></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.6.0/components/prism-markup.min.js" defer></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.6.0/components/prism-clike.min.js" defer></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.6.0/components/prism-java.min.js" defer></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.6.0/plugins/line-numbers/prism-line-numbers.min.js" defer></script>
<script src="../scripts/thymeleaf-articles.js" defer></script>
</head>
<body lang="en" class="article">
<div class="fluid-container toolbar-container">
<nav class="fluid-block toolbar">
<div class="toolbar-menu">
<div class="toolbar-menu-location">Docs</div>
<button id="site-menu-button" type="button" class="toolbar-menu-button">Site Menu</button>
</div>
<div id="site-menu" class="toolbar-menu-items">
<ul class="toolbar-links">
<li><a href="../../index.html" class="toolbar-link">Home</a></li>
<li><a href="../../download.html" class="toolbar-link">Download</a></li>
<li class="selected"><a href="../../documentation.html" class="toolbar-link">Docs</a></li>
<li><a href="../../ecosystem.html" class="toolbar-link">Ecosystem</a></li>
<li><a href="../../faq.html" class="toolbar-link">FAQ</a></li>
</ul>
<ul id="site-nav-links" class="toolbar-links">
<li><a href="https://bsky.app/profile/thymeleaf.org" class="toolbar-link">Bluesky</a></li>
<li><a href="https://github.com/thymeleaf" class="toolbar-link">GitHub</a></li>
</ul>
</div>
</nav>
</div>
<div class="hero-container fluid-container">
<header class="hero-header fluid-block">
<div class="hero-header-text">
<h1 class="hero-header-title">Thymeleaf</h1>
</div>
<div class="hero-header-image">
<img src="../images/thymeleaf.png" alt="Thymeleaf logo" class="hero-header-logo"/>
</div>
</header>
</div>
<div class="fluid-container">
<main class="fluid-block article">
<article>
<header>
<h1>Thymeleaf Page Layouts</h1>
<div class="article-author">
Written by Rafał Borowiec — <a href="http://blog.codeleak.pl">http://blog.codeleak.pl</a>
</div>
</header>
<section id="introduction" class="level2"><h2>Introduction</h2><p>Usually websites share common page components like the header, footer, menu and possibly many more. These page components can be used by the same or different layouts. There are two main styles of organizing layouts in projects: <em>include</em> style and <em>hierarchical</em> style. Both styles can be easily utilized with Thymeleaf without losing its biggest value: <strong>natural templating</strong>.</p><section id="include-style-layouts" class="level3"><h3>Include-style layouts</h3><p>In this style pages are built by embedding common page component code directly within each view to generate the final result. In Thymeleaf this can be done using <strong>Thymeleaf Standard Layout System</strong>:</p><pre class="xml"><code><body>
<div th:insert="footer :: copy">...</div>
</body></code></pre><p>The include-style layouts are pretty simple to understand and implement and in fact they offer flexibility in developing views, which is their biggest advantage. The main disadvantage of this solution, though, is that some code duplication is introduced so modifying the layout of a large number of views in big applications can become a bit cumbersome.</p></section><section id="hierarchical-style-layouts" class="level3"><h3>Hierarchical-style layouts</h3><p>In hierarchical style, the templates are usually created with a parent-child relation, from the more general part (layout) to the most specific ones (subviews; e.g. page content). Each component of the template may be included dynamically based on the inclusion and substitution of template fragments. In Thymeleaf this can be done using <strong>Thymeleaf Layout Dialect</strong>.</p><p>The main advantages of this solution are the reuse of atomic portions of the view and modular design, whereas the main disadvantage is that much more configuration is needed in order to use them, so the complexity of the views is bigger than with Include Style Layouts which are more “natural” to use.</p></section></section><section id="example-application" class="level2"><h2>Example Application</h2><p>All the samples and code fragments presented in this article are available on GitHub at <a href="https://github.com/thymeleaf/thymeleafexamples-layouts">https://github.com/thymeleaf/thymeleafexamples-layouts</a></p></section><section id="thymeleaf-standard-layout-system" class="level2"><h2>Thymeleaf Standard Layout System</h2><p>Thymeleaf Standard Layout System offers page fragment inclusion that is similar to <em>JSP includes</em>, with some important improvements over them.</p><section id="basic-inclusion-with-thinsert-and-threplace" class="level3"><h3>Basic inclusion with <code>th:insert</code> and <code>th:replace</code></h3><p>Thymeleaf can include parts of other pages as fragments (whereas JSP only includes complete pages) using <code>th:insert</code> (it will simply insert the specified fragment as the body of its host tag) or <code>th:replace</code> (will actually substitute the host tag by the fragment’s). This allows the grouping of fragments into one or several pages. Look at the example.</p><p>The <code>home/homeNotSignedIn.html</code> template is rendered when the anonymous user enters the home page of our application.</p><p>Class <code>thymeleafexamples.layouts.home.HomeController</code></p><pre class="java"><code>@Controller
class HomeController {
@GetMapping("/")
String index(Principal principal) {
return principal != null ? "home/homeSignedIn" : "home/homeNotSignedIn";
}
}</code></pre><p>Template <code>home/homeNotSignedIn.html</code></p><pre class="xml"><code><!DOCTYPE html>
<html>
<head>
...
</head>
<body>
...
<div th:replace="fragments/header :: header">
<!-- ============================================================================ -->
<!-- This content is only used for static prototyping purposes (natural templates)-->
<!-- and is therefore entirely optional, as this markup fragment will be included -->
<!-- from "fragments/header.html" at runtime. -->
<!-- ============================================================================ -->
<div class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="#">Static header</a>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li class="active"><a href="#">Home</a></li>
</ul>
</div>
</div>
</div>
</div>
<div class="container">
<div class="hero-unit">
<h1>Test</h1>
<p>
Welcome to the Spring MVC Quickstart application!
Get started quickly by signing up.
</p>
<p>
<a href="/signup" th:href="@{/signup}" class="btn btn-large btn-success">Sign up</a>
</p>
</div>
<div th:replace="fragments/footer :: footer">&copy; 2016 The Static Templates</div>
</div>
...
</body>
</html></code></pre><p>You can open the file directly in a browser:</p><figure><img src="images/layouts/homeNotSignedIn.png" alt="Home page when not signed in" /><figcaption>Home page when not signed in</figcaption></figure><p>In the above example, we are building a page that consists of page header and page footer. In Thymeleaf all fragments can be defined in a single file (e.g. <code>fragments.html</code>) or in a separate files, like in this particular case.</p><p>Let’s shortly analyze the inclusion statement:</p><pre class="xml"><code><div th:replace="fragments/header :: header">...</div></code></pre><p>The first part of the statement, <code>fragments/header</code>, is a template name that we are referencing. This can be a file (like in this example) or it can reference to the same file either by using the <code>this</code> keyword (e.g. <code>this :: header</code>) or without any keyword (e.g. <code>:: header</code>). The expression after double colon is a fragment selector (either fragment name or <em>Markup Selector</em>). As you can also see, the header fragment contains a markup that is used for static prototyping only.</p><p>Header and footer are defined in the following files:</p><p>Template <code>fragments/header.html</code></p><pre class="xml"><code><!DOCTYPE html>
<html>
<head>
...
</head>
<body>
<div class="navbar navbar-inverse navbar-fixed-top" th:fragment="header">
<div class="container">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".nav-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">My project</a>
</div>
<div class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li th:classappend="${module == 'home' ? 'active' : ''}">
<a href="#" th:href="@{/}">Home</a>
</li>
<li th:classappend="${module == 'tasks' ? 'active' : ''}">
<a href="#" th:href="@{/task}">Tasks</a>
</li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li th:if="${#authorization.expression('!isAuthenticated()')}">
<a href="/signin" th:href="@{/signin}">
<span class="glyphicon glyphicon-log-in" aria-hidden="true"></span>&nbsp;Sign in
</a>
</li>
<li th:if="${#authorization.expression('isAuthenticated()')}">
<a href="/logout" th:href="@{#}" onclick="$('#form').submit();">
<span class="glyphicon glyphicon-log-out" aria-hidden="true"></span>&nbsp;Logout
</a>
<form style="visibility: hidden" id="form" method="post" action="#" th:action="@{/logout}"></form>
</li>
</ul>
</div>
</div>
</div>
</body>
</html></code></pre><p>…which we can open directly in a browser:</p><figure><img src="images/layouts/header.png" alt="Header page" /><figcaption>Header page</figcaption></figure><p>And template <code>fragments/footer.html</code></p><pre class="xml"><code><!DOCTYPE html>
<html>
<head>
...
</head>
<body>
<div th:fragment="footer">
&copy; 2016 Footer
</div>
</body>
</html></code></pre><p>Note how that the referenced fragments are specified with <code>th:fragment</code> attributes. This way we can define multiple fragments in one template file, as it was mentioned earlier.</p><p>What is important here, is that all the templates can still be natural templates and can be viewed in a browser without a running server.</p></section><section id="including-with-markup-selectors" class="level3"><h3>Including with Markup Selectors</h3><p>In Thymeleaf, fragments don’t need to be explicitly specified using <code>th:fragment</code> at the page they are extracted from. Thymeleaf can select an arbitrary section of a page as a fragment (even a page living on an external server) by means of its Markup Selector syntax, similar to XPath expressions, CSS or jQuery selectors.</p><pre class="xml"><code><div th:insert="https://www.thymeleaf.org :: section.description" >...</div></code></pre><p>The above code will include a <code>section</code> with <code>class="description"</code> from <code>thymeleaf.org</code>.</p><p>In order to make it happen, the template engine must be configured with <code>UrlTemplateResolver</code>:</p><pre class="java"><code>@Bean
public SpringTemplateEngine templateEngine() {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.addTemplateResolver(new UrlTemplateResolver());
...
return templateEngine;
}</code></pre><p>For the Markup Selector syntax reference checkout this section in Thymeleaf documentation: <a href="http://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#appendix-c-markup-selector-syntax">Markup Selector syntax</a>.</p></section><section id="using-expressions" class="level3"><h3>Using expressions</h3><p>In <code>templatename :: selector</code>, both <code>templatename</code> and <code>selector</code> can be fully-featured expressions. In the below example we want to include different fragments depending on a condition. If the authenticated user is an Admin, we will show a different footer than for a regular user:</p><pre class="xml"><code><div th:replace="fragments/footer :: ${#authentication.principal.isAdmin()} ? 'footer-admin' : 'footer'">
&copy; 2016 The Static Templates
</div></code></pre><p><code>fragments/footer.html</code> has slightly changed, as we need to have two footers defined:</p><pre class="xml"><code><!DOCTYPE html>
<html>
<head>
...
</head>
<body>
<!-- /* Multiple fragments may be defined in one file */-->
<div th:fragment="footer">
&copy; 2016 Footer
</div>
<div th:fragment="footer-admin">
&copy; 2016 Admin Footer
</div>
</body>
</html></code></pre></section><section id="parameterized-inclusion" class="level3"><h3>Parameterized inclusion</h3><p>Fragments can specify arguments, just like methods. Whenever they are explicitly specified with a <code>th:fragment</code> attribute, they can provide an argument signature that can then be filled in with arguments from the calling <code>th:insert</code> or <code>th:replace</code> attributes.</p><p>Examples talk best. We can use parameterized inclusion in many contexts but one real life context is displaying messages on different pages of our application after successful form submission. Let’s look at the signup process in the application:</p><pre class="java"><code>@PostMapping("signup")
String signup(@Valid @ModelAttribute SignupForm signupForm,
Errors errors, RedirectAttributes ra) {
if (errors.hasErrors()) {
return SIGNUP_VIEW_NAME;
}
Account account = accountRepository.save(signupForm.createAccount());
userService.signin(account);
// see /WEB-INF/i18n/messages.properties and /WEB-INF/views/homeSignedIn.html
MessageHelper.addSuccessAttribute(ra, "signup.success");
return "redirect:/";
}</code></pre><p>As you can see, after a successful signup the user will be redirected to the home page with a flash attribute filled in. We want to create a reusable and parameterized fragment. This can be done as follows:</p><pre class="xml"><code><!DOCTYPE html>
<html>
<head>
...
</head>
<body>
<div class="alert alert-dismissable" th:fragment="alert (type, message)" th:assert="${!#strings.isEmpty(type) and !#strings.isEmpty(message)}" th:classappend="'alert-' + ${type}">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>
<span th:text="${message}">Test</span>
</div>
</body>
</html></code></pre><p>The above <code>alert</code> fragment takes two arguments: <code>type</code> and <code>message</code>. The <code>type</code> is the message type used for styling a message whereas the <code>message</code> is a text that will be shown to the user. We ensure that arguments exist and are not empty by using a <code>th:assert</code> attribute.</p><p>In order to include <code>alert</code> in any template we may write the following code (please note, that the value of a variable can be an expression):</p><pre class="xml"><code><div th:replace="fragments/alert :: alert (type='danger', message=${errorMessage})">...</div></code></pre><p>Parameterized fragments let developers create functional-like fragments that are easier to reuse. Read more about parameterized fragments in the Thymeleaf documentation: <a href="http://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#parameterizable-fragment-signatures">Parameterizable fragment signatures</a>.</p></section><section id="fragment-expressions" class="level3"><h3>Fragment Expressions</h3><p>Thymeleaf 3.0 introduced a new type of expression as a part of the general Thymeleaf Standard Expression system: <strong>Fragment Expressions</strong>:</p><pre class="xml"><code> <div th:insert="~{fragments/footer :: footer}">...</div></code></pre><p>The idea of this syntax is to be able to use resolved fragments as any other kind of objects in the template execution context for later use:</p><pre class="xml"><code><div th:replace="${#authentication.principal.isAdmin()} ? ~{fragments/footer :: footer-admin} : ~{fragments/footer :: footer-admin}">
&copy; 2016 The Static Templates
</div></code></pre><p>Fragment expression allows creating fragments in a way such that they can be enriched with markup coming from the calling templates, resulting in a layout mechanism that is far more flexible than <code>th:insert</code> and <code>th:replace</code>only.</p><section id="flexible-layout-example" class="level4"><h4>Flexible Layout Example</h4><p>The <code>task/layout.html</code> file defines all the fragments that will be used by calling templates. The below <code>header</code> fragment takes <code>breadcrumb</code> parameter that will replace <code>ol</code> markup with its resolved value:</p><pre class="xml"><code><!--/* Header fragment */-->
<div th:fragment="header(breadcrumb)">
<ol class="breadcrumb container" th:replace="${breadcrumb}">
<li><a href="#">Home</a></li>
</ol>
</div></code></pre><p>In the calling template (<code>task/task-list.html</code>) we will use a <em>Markup Selector</em> syntax to pass the element matching <code>.breadcrumb</code> selector:</p><pre class="xml"><code><!--/* The markup with breadcrumb class will be passed to the header fragment */-->
<header th:insert="task/layout :: header(~{ :: .breadcrumb})">
<ol class="breadcrumb container">
<li><a href="#">Home</a></li>
<li><a href="#" th:href="@{/task}">Tasks</a></li>
</ol>
</header></code></pre><p>As a result, the following HTML will be generated for the <code>task/tasks-list</code> view:</p><pre class="xml"><code><header>
<div>
<ol class="breadcrumb container">
<li><a href="#">Home</a></li>
<li><a href="[...]">Tasks</a></li>
</ol>
</div>
</header></code></pre><p>Similarily, we can use the same fragment with different breadcrumb in another view (<code>task/task.html</code>):</p><pre class="xml"><code><header th:insert="task/layout :: header(~{ :: .breadcrumb})">
<ol class="breadcrumb container">
<li><a href="#">Home</a></li>
<li><a href="#" th:href="@{/task}">Tasks</a></li>
<li th:text="${'Task ' + task.id}">Task</li>
</ol>
</header></code></pre><p>If there is nothing to be passed to the fragment, we can use a special empty fragment expression - <code>~{}</code>. It will pass an empty value that will be ignored in the <code>header</code> fragment:</p><pre class="xml"><code><header th:insert="task/layout :: header(~{})">
</header></code></pre><p>One another feature of the new fragment expression is so called <em>no-operation</em> token that allows the default markup of the fragment to be used in case this is needed:</p><pre class="xml"><code><header th:insert="task/layout :: header(_)">
</header></code></pre><p>As a result, we will get:</p><pre class="xml"><code><header>
<ol class="breadcrumb container">
<li><a href="#">Home</a></li>
</ol>
</header></code></pre><p>Fragment Expression enables the customization of fragments in ways that until now were only possible using the 3rd party Layout Dialect. Read more about this topic in the Thymeleaf documentation: <a href="http://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#flexible-layouts-beyond-mere-fragment-insertion">Flexible layouts: beyond mere fragment insertion</a></p></section></section><section id="fragment-inclusion-from-spring-controller" class="level3"><h3>Fragment inclusion from Spring <code>@Controller</code></h3><p>Fragments can be directly specified from a Spring MVC controller, i.e. <code>signup :: signupForm</code>; which can be useful for AJAX controllers that return only a small fragment of HTML to the browser. In the example below, the signup form fragment will be loaded upon AJAX request and the whole signup view - on regular request:</p><pre class="java"><code>@RequestMapping(value = "signup")
public String signup(Model model,
@RequestHeader("X-Requested-With") String requestedWith) {
model.addAttribute(new SignupForm());
if (AjaxUtils.isAjaxRequest(requestedWith)) {
return SIGNUP_VIEW_NAME.concat(" :: signupForm");
}
return SIGNUP_VIEW_NAME;
}</code></pre><p>The fragment is defined in <code>signup/signup.html</code>:</p><pre class="xml"><code><!DOCTYPE html>
<html>
<head>
...
</head>
<body>
<form method="post"
th:action="@{/signup}" th:object="${signupForm}" th:fragment="signupForm">
...
</form>
</body>
</html></code></pre><p>The above fragment is loaded when a new user wants to signup from a home page. The modal dialog will be shown upon clicking <code>Signup</code> button and the content will be loaded via AJAX call (see <code>home/homeNotSignedIn.html</code>).</p></section><section id="references" class="level3"><h3>References</h3><p>Please check Thymeleaf documentation that describes this topic very thoroughly.</p><ul><li><a href="http://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#template-layout">Template Layout</a>.</li><li><a href="http://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#fragment-specification-syntax">Fragment Expressions</a></li><li><a href="http://www.thymeleaf.org/doc/tutorials/3.0/usingthymeleaf.html#flexible-layouts-beyond-mere-fragment-insertion">Flexible layouts: beyond mere fragment insertion</a></li></ul></section><section id="thymol" class="level3"><h3>Thymol</h3><p>When a Thymeleaf template is used as a static prototype, we cannot see the fragments we are including using the <code>th:insert/th:replace</code> host tags. We can only see the fragments aside, opening their own template documents.</p><p>However, there is a way to see the real fragments included into our pages while prototyping. This can be done using <a href="http://www.thymeleaf.org/ecosystem.html#thymol">Thymol</a>, an unofficial JavaScript library that is an implementation of Thymeleaf’s standard fragment inclusion functionality, providing static support for some Thymeleaf attributes like <code>th:insert</code> or <code>th:replace</code>, conditional display with <code>th:if</code>/<code>th:unless</code>, etc.</p><p>As Thymol’s author states: <em>Thymol was created in order to provide a more accurate static representation of Thymeleaf’s dynamic templating capabilities by offering support for Thymeleaf attributes through a statically accessible javascript library</em></p><p>Thymol documentation and examples can be found on the official project site here: <a href="https://github.com/thymol/thymol.js">Thymol</a>.</p></section></section><section id="thymeleaf-layout-dialect" class="level2"><h2>Thymeleaf Layout Dialect</h2><p><a href="https://github.com/ultraq/thymeleaf-layout-dialect">Layout Dialect</a> gives people the possibility of using hierarchical approach, but from a Thymeleaf-only perspective and without the need to use external libraries, like Apache Tiles. Thymeleaf Layout Dialect uses layout/decorator templates to style the content, as well as it can pass entire fragment elements to included pages. Concepts of this library are similar to <a href="http://wiki.sitemesh.org">SiteMesh</a> or JSF with Facelets.</p><section id="configuration" class="level3"><h3>Configuration</h3><p>To get started with Layout Dialect we need to include it into the <code>pom.xml</code>. The dependency is:</p><pre class="xml"><code><dependency>
<groupId>nz.net.ultraq.thymeleaf</groupId>
<artifactId>thymeleaf-layout-dialect</artifactId>
<version>2.0.5</version>
</dependency></code></pre><p>We will also need to configure the integration by adding an additional dialect to our template engine:</p><pre class="java"><code>@Bean
public SpringTemplateEngine templateEngine() {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
...
templateEngine.addDialect(new LayoutDialect());
return templateEngine;
}</code></pre><p>No other changes are required.</p></section><section id="creating-a-layout" class="level3"><h3>Creating a layout</h3><p>The layout file is defined in <code>/WEB-INF/views/task/layout.html</code>:</p><pre class="xml"><code><!DOCTYPE html>
<html>
<head>
<!--/* Each token will be replaced by their respective titles in the resulting page. */-->
<title layout:title-pattern="$LAYOUT_TITLE - $CONTENT_TITLE">Task List</title>
...
</head>
<body>
<!--/* Standard layout can be mixed with Layout Dialect */-->
<div th:replace="fragments/header :: header">
...
</div>
<div class="container">
<div layout:fragment="content">
<!-- ============================================================================ -->
<!-- This content is only used for static prototyping purposes (natural templates)-->
<!-- and is therefore entirely optional, as this markup fragment will be included -->
<!-- from "fragments/header.html" at runtime. -->
<!-- ============================================================================ -->
<h1>Static content for prototyping purposes only</h1>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Praesent scelerisque neque neque, ac elementum quam dignissim interdum.
Phasellus et placerat elit. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Praesent scelerisque neque neque, ac elementum quam dignissim interdum.
Phasellus et placerat elit.
</p>
</div>
<div th:replace="fragments/footer :: footer">&copy; 2014 The Static Templates</div>
</div>
</body>
</html></code></pre><p>We can open the file directly in a browser:</p><figure><img src="images/layouts/layoutlayoutdialect.png" alt="Layout page" /><figcaption>Layout page</figcaption></figure><p>The above file is our decorator for content pages we will be creating in the application. The most important thing about the above example is <code>layout:fragment="content"</code>. This is the <em>heart</em> of the decorator page (layout). You can also notice, that header and footer are included using Standard Thymeleaf Layout System.</p><p>The content page looks as follows (<code>WEB-INF/views/task/list.html</code>):</p><pre class="xml"><code><!DOCTYPE html>
<html layout:decorate="~{task/layout}">
<head>
<title>Task List</title>
...
</head>
<body>
<!-- /* Content of this page will be decorated by the elements of layout.html (task/layout) */ -->
<div layout:fragment="content">
<table class="table table-bordered table-striped">
<thead>
<tr>
<td>ID</td>
<td>Title</td>
<td>Text</td>
<td>Due to</td>
</tr>
</thead>
<tbody>
<tr th:if="${tasks.empty}">
<td colspan="4">No tasks</td>
</tr>
<tr th:each="task : ${tasks}">
<td th:text="${task.id}">1</td>
<td><a href="view.html" th:href="@{'/' + ${task.id}}" th:text="${task.title}">Title ...</a></td>
<td th:text="${task.text}">Text ...</td>
<td th:text="${#calendars.format(task.dueTo)}">July 11, 2012 2:17:16 PM CDT</td>
</tr>
</tbody>
</table>
</div>
</body>
</html></code></pre><p>And in the browser it looks like this:</p><figure><img src="images/layouts/layoutlayoutdialectlist.png" alt="Layout page" /><figcaption>Layout page</figcaption></figure><p>Content of this <code>task/list</code> view will be decorated by the elements of <code>task/layout</code> view. Please note <code>layout:decorate="~{task/layout}"</code> attribute in <code><html></code> element. This attribute signals to the Layout Dialect which layout should be used to decorate given view. And please note it is using Thymeleaf Fragment Expression syntax.</p><p>And what about <em>Natural Templates</em> using the Layout Dialect? Again, possible! You simply need to add some prototyping-only markup around the fragments being included in your templates and that’s it!</p></section><section id="include-style-approach-with-layout-dialect" class="level3"><h3>Include style approach with Layout Dialect</h3><p>Layout Dialect supports not only hierarchical approach – it also provides a way to use it in an include-style way (<code>layout:include</code>). Comparing with standard Thymeleaf includes, with Layout Dialect you can pass HTML elements to the included page. Useful if you have some HTML that you want to reuse, but whose contents are too complex to pass by means of parameterized inclusion in standard Thymeleaf dialect.</p><p>This is an example of a reusable alert fragment using <code>layout:fragment</code> (<code>task/alert.html</code>):</p><pre class="xml"><code><!DOCTYPE html>
<html>
<body>
<th:block layout:fragment="alert-content">
<p>Duis mollis, est non commodo luctus, nisi erat porttitor ligula...</p>
<p>
<button type="button" class="btn btn-danger">Take this action</button>
<button type="button" class="btn btn-default">Or do this</button>
</p>
</th:block>
</body>
</html></code></pre><p>The calling of the above fragment may look as follows (<code>task/list.html</code>):</p><pre class="xml"><code> <div layout:insert="~{task/alert :: alert}" th:with="type='info', header='Info'" th:remove="tag">
<!--/* Implements alert content fragment with simple content */-->
<th:block layout:fragment="alert-content">
<p><em>This is a simple list of tasks!</em></p>
</th:block>
</div></code></pre><p>Or:</p><pre class="xml"><code> <div layout:insert="~{task/alert :: alert}" th:with="type='danger', header='Oh snap! You got an error!'" th:remove="tag">
<!--/* Implements alert content fragment with full-blown HTML content */-->
<th:block layout:fragment="alert-content">
<p>Duis mollis, est non commodo luctus, nisi erat porttitor ligula...</p>
<p>
<button type="button" class="btn btn-danger">Take this action</button>
<button type="button" class="btn btn-default">Or do this</button>
</p>
</th:block>
</div></code></pre><p>In this case, the entire <code>alert-content</code> of <code>task/alert</code> (<code>/WEB-INF/views/task/alert.html</code>) template will be replaced by custom HTML above.</p></section><section id="references-1" class="level3"><h3>References</h3><p>Please check out the Layout Dialect documentation that describes this topic very thoroughly. You will definitively find some more advanced examples than in this article.</p><p>You can find the documentation here: <a href="https://github.com/ultraq/thymeleaf-layout-dialect">Layout Dialect</a>.</p></section></section><section id="other-layout-options" class="level2"><h2>Other Layout Options</h2><p>For some of the developers neither of the solutions presented before is sufficient. Thymeleaf Standard Layout System is not enough and using external libraries is not an option. In that case, the custom solution may be the way to go.</p><section id="thymeleaf-custom-layout" class="level3"><h3>Thymeleaf Custom Layout</h3><p>One of such a solutions is well described in this blog post: <a href="http://blog.codeleak.pl/2013/11/thymeleaf-template-layouts-in-spring.html">Thymeleaf template layouts in Spring MVC application with no extensions</a>. The idea of this solution is really simple. Let’s visualize that with an example:</p><p>Example view file (1):</p><pre class="xml"><code><!DOCTYPE html>
<html>
<head>
...
</head>
<body>
<div class="container" th:fragment="content">
<p>
Hello <span th:text="${#authentication.name}">User</span>!
Welcome to the Spring MVC Quickstart application!
</p>
</div>
</body>
</html></code></pre><p>And the layout file (2):</p><pre class="xml"><code><!DOCTYPE html>
<html>
<head>
...
</head>
<body>
<div th:replace="fragments/header :: header">Header</div>
<div th:replace="${view} :: content">Page Content</div>
<div th:replace="fragments/footer :: footer">Footer</div>
</body>
</html></code></pre><p>What will happen?</p><ul><li>Controllers return view names, that translate to single Thymeleaf view file (1)</li><li>Before rendering the view, the original <code>viewName</code> attribute in <code>ModelAndView</code> object is replaced with with the name of the layout view and the original <code>viewName</code> becomes an attribute in <code>ModelAndView</code>.</li><li>The layout view (2) contains several include elements: <code><div th:replace="${view} :: content">Page Content</div></code></li><li>The actual view file contains fragments, <em>pulled</em> by the template which embeds the actual view</li></ul><p>The project can be found on <a href="https://github.com/kolorobot/thymeleaf-custom-layout">GitHub</a>.</p></section></section><section id="summary" class="level2"><h2>Summary</h2><p>In this article, we described many ways of achieving the same: <strong>layouts</strong>. You can build layouts using Thymeleaf Standard Layout System that is based on include-style approach. You also have powerful Layout Dialect, that uses decorator pattern for working with layout files. Finally, you can easily create your own solution.</p><p>Hopefully, this article gives you some more insights on the topic and you will find your preferred approach depending on your needs.</p></section>
</article>
</main>
</div>
<div class="fluid-container footer-container">
<footer class="footer fluid-block">
<div class="footer-sections">
<h5>On this site</h5>
<ul class="footer-sections-links">
<li><a href="../../index.html">Home</a></li>
<li><a href="../../download.html">Download</a></li>
<li><a href="../../documentation.html">Docs</a></li>
<li><a href="../../ecosystem.html">Ecosystem</a></li>
<li><a href="../../faq.html">FAQ</a></li>
<li id="footer-issue-tracking"><a href="../../issuetracking.html">Issue Tracking</a></li>
<li><a href="../../team.html">The Thymeleaf Team</a></li>
<li><a href="../../whoisusingthymeleaf.html">Who's using Thymeleaf?</a></li>
</ul>
</div>
<div>
<h5>External links</h5>
<ul class="footer-sections-links">
<li><a href="https://bsky.app/profile/thymeleaf.org">Follow us on Bluesky</a></li>
<li><a href="https://github.com/thymeleaf">Fork us on GitHub</a></li>
</ul>
</div>
</footer>
<div class="copyright fluid-block">Copyright © Thymeleaf</div>
<div class="license fluid-block">
Thymeleaf is <strong>open source</strong> software distributed under the
<a href="https://www.apache.org/licenses/LICENSE-2.0.html">Apache License 2.0</a><br/>
This website (excluding the names and logos of Thymeleaf users) is licensed under the <a href="http://creativecommons.org/licenses/by-sa/3.0/">CC BY-SA 3.0 License</a>
</div>
</div>
</body>
</html>