Search code examples
javahtmlmodel-view-controllerthymeleaf

How to use multiple "components"(files) with dynamic data on Thymeleaf?


I'm setting up a new website and I'd like to have multiple files for re-usable components (header, footer, common imports on , scripts, etc). I also want to have files for each layout (combining the components I defined), like "Main Application", "Full Screen Dashboard" etc. Finnaly, I want to create pages with just the content I need and then plug that into the layout of my choice.

Something like this: Diagram of files

I have looked at this question but that requires me to call on java the layout with my content as a parameter instead of defining on my content what layout to use.

I've also considered just using my "layouts" as templates, then copy+paste into a new "content" file and edit the placeholders out.

This is a sample controller:

    @View("ticketing/home")
    @Get("/")
    public HttpResponse home() {
        final Map<String, Object> resultMap = new HashMap<>();

        resultMap.put("requests", ticketingClient.getSummaryRequests()); //Returns a list of tickets to be displayed.
        return HttpResponse.ok(resultMap); //Maps the resultMap variable to the ticketing/home thymeleaf template
    }

This is how I'd like to have my content file structured:

<html>
    <head>
        <title>Tickets</title>
    </head>
<body>
    <div>
        <p>My Content is here!</p>
        <img src="wow.jpeg">
    </div>
</body>
</html>

this is what I have now for my layout:

<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Application Layout</title>
    <th:block th:include="./fragments/head.html :: imports"></th:block>
</head>
<body>
    <div id="wrapper">
        <!-- Sidebar Menu -->
        <div th:replace="./fragments/sidebar.html :: sidebar"></div>
        <div id="page-wrapper" class="gray-bg">
            <!-- Navigation Menu -->
            <div th:replace="./fragments/topbar.html :: topbar"></div>
            <!-- Page Header -->
            <div th:replace="./fragments/pageheader.html :: pageheader"></div>
            <!-- Main Content -->
            <div class="wrapper wrapper-content animated fadeInUp" th:fragment="content">
                <div class="row">
                    <div class="col-lg-12">
                        <div class="wrapper wrapper-content">
                            <p>Content here</p>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
    <div th:replace="./fragments/footer.html :: footer"></div>
    <th:block th:replace="./fragments/scripts.html :: scripts"></th:block>
</body>
</html>

I expect to be able to use the same java syntax (with the annotations) to use my views.

I'd rather change more in the content file than on the layout file.


Solution

  • I found an answer that worked for me:

    I've referenced this post, this other post and the post mentioned on the question.

    Here's some code: component: content

    <!DOCTYPE HTML>
    <html xmlns:th="http://www.thymeleaf.org">
    <head>
    <title>Content Wrapper</title>
    </head>
    <body>
        <div class="wrapper wrapper-content animated fadeInUp" th:fragment="content(content)">
            <!-- Main Content -->
            <div class="row">
                <div class="col-lg-12">
                    <div class="wrapper wrapper-content">
                        <th:block th:replace="${content}"></th:block>
                    </div>
                </div>
            </div>
        </div>
    </body>
    </html>
    

    component: pageheader

    <!DOCTYPE HTML>
    <html xmlns:th="http://www.thymeleaf.org" th:fragment="pageheader">
    <head>
    <title>Page Header</title>
    </head>
    <body>
        <div class="row wrapper border-bottom white-bg page-heading" th:fragment="pageheader(title)">
            <div class="col-sm-8 col-lg-9">
                <!-- Title -->
                <h2 th:replace="${title}">This is main title</h2>
                <th:block th:if="${breadcrumb}">
                    <!-- Breadcrumb -->
                    <ol class="breadcrumb" th:replace="${breadcrumb}">
                        <li class="breadcrumb-item"><a href="index.html">This is</a></li>
                        <li class="breadcrumb-item active"><strong>Breadcrumb</strong></li>
                    </ol>
                </th:block>
            </div>
            <div class="col-sm-4 col-lg-3">
                <th:block th:if="${actions}">
                    <!-- Main Action -->
                    <div class="title-action btn-group" th:if="${actions}" th:replace="${actions}">
                        <a href="" class="btn btn-primary">Main Actions</a>
                        <a href="" class="btn btn-primary">Main Actions</a>
                    </div>
                </th:block>
            </div>
        </div>
    </body>
    </html>
    

    layout: applayout

    <!DOCTYPE HTML>
    <html xmlns:th="http://www.thymeleaf.org" th:fragment="layout"> <!-- ${content} and ${title} are mandatory -->
    <head>
    <title th:replace="${title}">Application Layout</title>
    <th:block th:include="./fragments/head.html :: imports"></th:block>
    </head>
    <body>
        <div id="wrapper">
            <!-- Sidebar Menu -->
            <div th:replace="./fragments/sidebar.html :: sidebar"></div>
            <div id="page-wrapper" class="gray-bg">
                <!-- Navigation Menu -->
                <div th:replace="./fragments/topbar.html :: topbar"></div>
                <!-- Page Header -->
                <div th:replace="./fragments/pageheader.html :: pageheader(title=${title},breadcrumb=${breadcrumb},actions=${actions})"></div>
                <!-- Main Content -->
                <div class="wrapper wrapper-content animated fadeInUp" th:replace="./fragments/content.html :: content(${content})"></div>
            </div>
        </div>
        <div th:replace="./fragments/footer.html :: footer"></div>
        <th:block th:replace="./fragments/scripts.html :: scripts"></th:block>
    </body>
    </html>
    

    content: tickets

    <!DOCTYPE html>
    <html lang="en" th:replace="~{layout/applayout.html :: layout(title=~{::title},breadcrumb=~{::ol},content=~{::#main},scripts=~{::#scripts})}" xmlns:th="http://www.thymeleaf.org">
    <head>
    <title>Homepage</title>
    </head>
    <body>
        <ol class="breadcrumb">
            <li class="breadcrumb-item"><a href="index.html">Home</a></li>
            <li class="breadcrumb-item active"><strong>wow</strong></li>
        </ol>
        <div id="main">
            <div class="col">
                <p>WOW</p>
            </div>
            <div class="col">
                <p>WOW</p>
            </div>
            <div class="col">
                <p>WOW</p>
            </div>
        </div>
        <th:block id="scripts">
        </th:block>
    </body>
    </html>
    

    This way I can have optional parameters on my content page that get treated by each fragment individually.