Govinda Sakhare
Govinda Sakhare

Reputation: 5739

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?

Upvotes: 3

Views: 9699

Answers (2)

dsharew
dsharew

Reputation: 10665

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

Upvotes: 2

sagar limbu
sagar limbu

Reputation: 1262

if you are using spring boot you don't need to define view resolver to render html pages.

The @SpringBootApplication annotation is equivalent to using @Configuration, @EnableAutoConfiguration and @ComponentScan. so you don't probably need to define @ComponentScan.

I also see you don't need to define those beans if you are using spring boot. templates directory under resources are directly accessible without defining beans. so by using thymeleaf you can return views from controller easily.

views are kept under src/main/web/WEB-INF in spring mvc. but on spring boot views are kept under templates directory which can be returned from controller without doing any view resolver configuration.

for your thymeleaf problem you can see this on github.

https://github.com/sagar1limbu/bookstore

Upvotes: 0

Related Questions