Spring boot: thymeleaf not returning fragments correctly
I am creating a spring bootable web application. For template purposes, I am using thymeleaf. I am creating a separate snippet of an html page and I am creating two different layouts from these snippets. However, when I connect the content page, I am not getting the proper output.
Please check the code snippets /
view resolver
@Configuration
@EnableAutoConfiguration
@PropertySource("classpath:application.properties")
public class WebConfig extends WebMvcConfigurerAdapter {
@Bean
public TemplateResolver templateResolver() {
ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver();
templateResolver.setPrefix("/WEB-INF/views/");
templateResolver.setSuffix(".html");
templateResolver.setTemplateMode("html5");
return templateResolver;
}
@Bean
public SpringTemplateEngine setTemplateEngine() {
SpringTemplateEngine springTemplateEngine = new SpringTemplateEngine();
springTemplateEngine.setTemplateResolver(templateResolver());
return springTemplateEngine;
}
@Bean
public ViewResolver viewResolver(){
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setTemplateEngine(setTemplateEngine());
viewResolver.setOrder(1);
return viewResolver;
}
Directory structure
src/
main/
webapp/
WEB-INF/
views/
fragments/
header.html
footer.html
navBar.html
layouts/
appLayout.html
baseLayout.html
welcome.html
appLayout.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head>
</head>
<body>
<div th:replace="/fragments/header :: header"></div>
<div th:replace="/fragments/navbar :: navbar"></div>
<div class="container">
<div layout:fragment="content">
<p>Content goes here...</p>
</div>
<footer>
<div th:replace="/fragments/footer :: footer"></div>
</footer>
</div>
</body>
</html>
Footer footer.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
</head>
<body>
<div th:fragment="footer">
Footer
</div>
</body>
</html>
Various fragments are created with this method.
aka startup main method class
@ComponentScan(basePackages={"xyz.abc"})
@SpringBootApplication
public class AppApplication {
public static void main(String[] args) {
System.out.println("Started");
SpringApplication.run(AppApplication.class, args);
}
}
welcome.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorate="~{layouts/appLayout}">
<head>
</head>
<body>
<th:block layout:fragment="content">
<div class="hero-unit">
<h1>My Content</h1>
</div>
</th:block>
</body>
</html>
Now when I access the server, I get nothing welcome.html
but nothing from appLayout
andfragments
What am I missing?
Another problem I went through many projects and some keep views
under src/main/resource
and I keep it under src/main/web/WEB-INF
what is the difference between these two approaches?
source to share
Here's a complete example.
Gradle Dependency:
compile('org.springframework.boot:spring-boot-starter-thymeleaf')
If you want to use spring-free dialect [optional] add this:
compile("org.thymeleaf.extras:thymeleaf-extras-springsecurity4:2.1.2.RELEASE")
In SpringBoot you don't need to look at the config, all your views should go: src / resources / templates
Does this mean that all views in the template directory are public?
Not.
Where do I put my public static files (js and css)?
Src / resources / templates / static
How do I create layouts?
Thymleaf makes it easy to handle the vertical and horizontal relationships of image slices.
1. Ratio of vertical layout:
Say you have a master page where you include all your common js and css files, third party nav, nav app bar ... etc. This point of view will vertically enter almost all of yours so in this case you want to somehow embed all of your views within your home page. Let's say you have an employee_list page; when a user requests the employee_list page, the employee_list page will be inserted into your main layout and the user will see the employee_list page + main page items.
Master Page:
On this page, all you have to do is add layout:fragment="content"
. content
is the name of the child view fragment that will be embedded in this page.
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
xmlns:sec="http://www.w3.org/1999/xhtml">
<head>
</head>
<body>
<div id = "page_wrapper">
<div id="side_menu">
<!--side menu content-->
</div>
<div id="content_wrapper">
<!--page content-->
<div layout:fragment="content">
</div>
</div>
<script type="text/javascript" th:inline="javascript">
//common js here
/* ]]> */
</script>
</div>
</body>
</html>
Employee list page:
To put the employee_list page in master, all you have to do is add layout:decorator="layouts/master"
to the html
page tag . I put all my layouts in the src / resources / templates / layouts directory, so I use layouts / master instead of just saying master. Also notice the attribute layout:fragment="content"
; this attribute indicates what will be embedded in the master page in this view.
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorator="layouts/master">
<body>
<div id = "content" class = "content layout-list" layout:fragment="content">
<div>
<table>
</table>
</div>
</div>
</body>
</html>
Horizontal layout ratio:
What if you want to include a generic view instead of vertical inheritance like in the previous example? Let's say in the above example you want to enable pageview on the employee_list page; in this case, the relationship becomes horizontal.
Snippet of pagination:
This is a working example btw; class Page
is my own custom class that I created to wrap the paged responses. This is a very simple class that I am making, so I put the answer list and pagination information in it. The fragment name is specified usingth:fragment="pagination"
<div th:if = "${page.totalNumberOfItems > 0 and page.numberOfItems > 0}" class = "pagination-wrapper" th:fragment="pagination" xmlns:th="http://www.thymeleaf.org">
<div class = "valign-wrapper right">
<div id = "items_per_page_wrapper">
<div class="valign-wrapper">
<span th:text="#{ui.pagination.rows.per.page.label}" style="padding-right: 10px">Rows per page:</span>
<select id="items_per_page_select" name = "itemsPerPage">
<option th:each="items_per_page : ${page.ITEMS_PER_PAGE_OPTIONS_LIST}" th:text="${items_per_page}" th:value = "${items_per_page}" th:selected="${items_per_page} == ${page.itemsPerPage}">10</option>
</select>
</div>
</div>
<label class="current_page_info"
th:text="#{ui.pagination.current.page.info(${page.currentPageFirstItemNumber}, ${page.currentPageLastItemNumber}, ${page.totalNumberOfItems})}"></label>
<ul class="pagination right">
<li th:classappend="${page.pageNumber > 0 } ? '' : 'disabled'">
<a id="previous_page_link" href="javascript:void(0)">
<i class="material-icons">navigate_before</i>
</a>
</li>
<li th:classappend="${page.currentPageLastItemNumber == page.totalNumberOfItems} ? 'disabled' : ''">
<a id="next_page_link" href="javascript:void(0)">
<i class="material-icons">navigate_next</i>
</a>
</li>
</ul>
</div>
</div>
Employee List Page:
Add this snippet below the table tag.
<div th:replace="common/pagination :: pagination">
<!--pagination content-->
</div>
source to share
if you are using spring boot you don't need to define a view handler to render html pages.
The @SpringBootApplication annotation is equivalent to using @Configuration, @EnableAutoConfiguration, and @ComponentScan. so you probably don't need to define @ComponentScan.
I can also see that you don't need to define those beans if you are using spring boot. the resource template directory is directly accessible without defining beans. so using thymeleaf you can easily get the view from the controller.
Representation
are stored in src / main / web / WEB-INF in spring mvc. but in spring boot views are stored in templates directory which can be returned from controller without any permission configuration.
for your thymelean problem you can see this on github.
source to share