Spring Boot + Bootstrap + Thymeleaf Modal

July 06, 2020 No comments Spring Boot Bootstrap Thymeleaf Modal

Spring Boot + Bootstrap + Thymeleaf Modal

1. Introduction

In this article, we are going to present the Thymeleaf Modal component based on the Bootstrap framework and Spring Boot application server. We will make a use of Thymeleaf Fragments to create injectable piece of HTML document that will contain a modal code.

If you are looking for more useful articles about Thymeleaf, please check the following links:
Thymeleaf Article List
Building forms with Thymeleaf

2. Dependencies

2.1. Maven dependencies

To present how the Thymeleaf Modal component works with Thymeleaf we used a simple Spring Boot application created as a Maven project with the following dependencies:

2.2. Front-End libraries
  • bootstrap - a frontend framework for creating responsive websites.

Project Maven pom.xml file has the following structure:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <artifactId>thymeleaf-bootstrap-modal</artifactId>

    <properties>
        <bootstrap.version>4.0.0-2</bootstrap.version>
        <webjars-locator.version>0.30</webjars-locator.version>
        <lombok.version>1.18.2</lombok.version>
        <commonmark.version>0.13.1</commonmark.version>
    </properties>

    <!-- Inherit defaults from Spring Boot -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.5.RELEASE</version>
    </parent>

    <!-- Add typical dependencies for a web application -->
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>bootstrap</artifactId>
            <version>${bootstrap.version}</version>
        </dependency>

        <dependency>
            <groupId>org.webjars</groupId>
            <artifactId>webjars-locator</artifactId>
            <version>${webjars-locator.version}</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <!-- Package as an executable jar -->
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

3. Web Controller and Main Application class

The main controller that serves index.html template on the root context is called: IndexController:

package com.frontbackend.thymeleaf.bootstrap.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/")
public class IndexController {

    @GetMapping
    public String main(Model model) {
        return "index";
    }
}

To show how to create modals with dynamic content we used ModalController that handles GET requests to /modals/modal1 and /modals/modal2 path:

package com.frontbackend.thymeleaf.bootstrap.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
@RequestMapping("modals")
public class ModalController {

    @GetMapping("modal1")
    public String modal1() {
        return "modal1";
    }

    @GetMapping("modal2")
    public String modal2(@RequestParam("name") String name, Model model) {
        model.addAttribute("name", name);
        return "modal2";
    }
}

The Application is the Java class with the main method that starts the Spring Boot application server:

package com.frontbackend.thymeleaf.bootstrap;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

4. Templates

In the presentation layer we have several Thymeleaf templates:

  • index.html - the main view with buttons to fire modals,
  • modal1.html - content for modal 1,
  • modal1.html - content for modal 2,
  • _modals.html - HTML with Thymeleaf fragment code.

The index.html template has the following structure:

<!DOCTYPE HTML>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8"/>
    <title>Spring Boot Thymeleaf Application - Bootstrap Modal</title>

    <link th:rel="stylesheet" th:href="@{/webjars/bootstrap/4.0.0-2/css/bootstrap.min.css} "/>
</head>
<body>

<!-- Navigation -->
<nav class="navbar navbar-expand-lg navbar-dark bg-dark static-top">
    <div class="container">
        <a class="navbar-brand" href="/">Thymeleaf - Bootstrap Modal</a>
        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarResponsive"
                aria-controls="navbarResponsive"
                aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="navbarResponsive">
            <ul class="navbar-nav ml-auto">
                <li class="nav-item active">
                    <a class="nav-link" href="#">Home
                        <span class="sr-only">(current)</span>
                    </a>
                </li>
                <li class="nav-item">
                    <a class="nav-link" href="#">About</a>
                </li>
                <li class="nav-item">
                    <a class="nav-link" href="#">Services</a>
                </li>
                <li class="nav-item">
                    <a class="nav-link" href="#">Contact</a>
                </li>
            </ul>
        </div>
    </div>
</nav>

<div class="container">
    <div class="row mt-5">
        <div class="col">
            <!-- Button trigger modal -->
            <button type="button" class="btn btn-primary" data-toggle="modal" data-target="#exampleModal">
                Launch simple modal
            </button>

            <button type="button" class="btn btn-primary" data-toggle="modal" data-target="#exampleModal1">
                Dynamic modal 1
            </button>

            <button type="button" class="btn btn-primary" data-toggle="modal" data-target="#exampleModal2">
                Dynamic modal 2
            </button>

            <th:block th:replace="_modals :: modal('exampleModal')">modal</th:block>
            <th:block th:replace="_modals :: modal('exampleModal1')">modal</th:block>
            <th:block th:replace="_modals :: modal('exampleModal2')">modal</th:block>
        </div>
    </div>
</div>


<script th:src="@{/webjars/jquery/jquery.min.js}"></script>
<script th:src="@{/webjars/popper.js/umd/popper.min.js}"></script>
<script th:src="@{/webjars/bootstrap/js/bootstrap.min.js}"></script>

<script>
    $('#exampleModal1').on('show.bs.modal', function () {
        $.get("/modals/modal1", function (data) {
            $('#exampleModal1').find('.modal-body').html(data);
        })
    });

    $('#exampleModal2').on('show.bs.modal', function () {
        var name = prompt("Please enter your name", "John Doe");
        $.get("/modals/modal2?name=" + name, function (data) {
            $('#exampleModal2').find('.modal-body').html(data);
        })
    })
</script>

</body>
</html>

modal1.html - contains a simple text content:

Dynamic content for Modal 1

modal2.html - contains sample Thymeleaf attribute that will display a name user prompt:

Hello <strong th:text="${name}"></strong>! Nice to see you.

_modals.html - contains fragment with Bootstrap modal code:

<!DOCTYPE HTML>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
</head>
<body>
<!-- Modal -->
<div th:fragment="modal(id)" class="modal fade" th:id="${id}" tabindex="-1" role="dialog" aria-labelledby="exampleModalLabel"
     aria-hidden="true">
    <div class="modal-dialog">
        <div class="modal-content">
            <div class="modal-header">
                <h5 class="modal-title" id="exampleModalLabel">Modal title</h5>
                <button type="button" class="close" data-dismiss="modal" aria-label="Close">
                    <span aria-hidden="true">&times;</span>
                </button>
            </div>
            <div class="modal-body">
                Cras mattis consectetur purus sit amet fermentum. Cras justo odio, dapibus ac facilisis in,
                egestas eget quam. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
                <button type="button" class="btn btn-primary">Save changes</button>
            </div>
        </div>
    </div>
</div>
</body>
</html>

5. The output

The running application is available under http://locahost:8080 URL and presents the following functionality:

Thymeleaf bootstrap modal

6. Conclusion

In this article, we showcased how to use Bootstrap Modal in Thymeleaf templates. We used fragments to separate HTML code required for generating modal.

As usual, the code used in this article is available under our GitHub repository

{{ message }}

{{ 'Comments are closed.' | trans }}