Search code examples
springspring-mvcspring-bootthymeleaf

Spring boot: thymeleaf is not rendering fragments correctly


I am creating the spring boot web Application. For templating purpose I am using thymeleaf. I am creating separate html page fragment and I am creating two different layout from these fragments. However, When I connecting the content page I am not getting 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 fragment footer.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
</head>
<body>
    <div th:fragment="footer">
    Footer
    </div>
</body>
</html>

Different fragments are created using this method.

main method class aka startup

@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 am getting only welcome.html and nothing from appLayout and fragments

What am I missing?
One more issue I have gone through many projects and some of them are keeping views under src/main/resource and I am keeping it under src/main/web/WEB-INF what is the difference between these two approaches?


Solution

  • Here is a complete example for you.

    Gradle Dependency:

    compile('org.springframework.boot:spring-boot-starter-thymeleaf')
    

    If you want to use spring-security dialect [optional] add this:

      compile("org.thymeleaf.extras:thymeleaf-extras-springsecurity4:2.1.2.RELEASE")
    

    On SpringBoot you dont need view configuration all your views should go to: src/resources/templates

    enter image description here

    Does this mean all views under templates dir are public?
    No.

    Where do I put publicly accessible static files(js & css)?
    src/resources/templates/static

    How to create Layouts?

    In Thymleaf it is easy to handle vertical and horizontal view fragment relationships.

    1. Vertical Layout Relationship:

    Say you have a master page where you include all your common js and css files, side nav, app bar nav ..etc. This view will be vertically in herited by almost all of your views so in this case you want to somehow embed all your views within your master page. Say you have employee_list page; when the user requests for the employee_list page, the employee_list page will be inserted into your main layout and the user will be seeing employee_list page + master page elements.

    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>
    

    enter image description here

    Employee List Page:
    To put employee_list page with in the master all you need to do is add layout:decorator="layouts/master" to the html tag of the page. I have put all my layouts under src/resources/templates/layouts dir that why I am using layouts/master instead just saying master. Also pay attention to the layout:fragment="content" attribute; this attribute is indicating whatever is in this view will be embedded on the master page.

     <!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>
    

    enter image description here
    Result:

    enter image description here


    Horizontal Layout Relationship:

    What if you want to include a common view instead of vertically inheriting it like we did on the previous example? Say on the above example you want to include a pagination view on your employee_list page; in this case the relationship becomes horizontal.

    Pagination Fragment:
    This is a working example btw; the Page class is my own custom class that I created to wrap paginated responses. It is very simple class all I do is I put the response list and pagination info in it. Fragment name is indicated using th: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 under the table tag.

      <div th:replace="common/pagination :: pagination">
            <!--pagination content-->
        </div>
    

    Result:
    enter image description here