Spring Boot + Bootstrap + Thymeleaf Toast

July 06, 2020 No comments Spring Boot Bootstrap Thymeleaf Toast

Spring Boot + Bootstrap + Thymeleaf Toast

1. Introduction

This article covers the Thymeleaf Toast component. Toast is a small message that shows up in a box at the bottom or top of the screen and disappears on its own after a few seconds.

Find more useful informations about Thymeleaf and Forms in the following links:
Awesome Thymeleaf Articles
Forms in Thymeleaf

2. Dependencies

2.1. Maven dependencies

To present Toast component we used a sample Spring Boot application created as a Maven project using four main dependencies:

2.2. Front-End libraries
  • bootstrap - a frontend framework for creating responsive websites,
  • iziToast - a JavaScript library for creating awesome toasts.

Project Maven pom.xml file comes with 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-toast</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>
        <commons-validator.version>1.6</commons-validator.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>commons-validator</groupId>
            <artifactId>commons-validator</artifactId>
            <version>${commons-validator.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. Model, Controller, and Main Application class

In the model layer we use single object EmailContainer just to wrap an email address using JSON format:

package com.frontbackend.thymeleaf.bootstrap.model;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Setter
@Getter
@NoArgsConstructor
public class EmailContainer {

    private String email;
}

The main page is served under the root context using IndexController class:

package com.frontbackend.thymeleaf.bootstrap.controller;

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

@Controller
@RequestMapping({ "/", "/index" })
public class IndexController {

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

Ajax requests are handled by EmailRestController class that has a responsibility of validating provided email address:

package com.frontbackend.thymeleaf.bootstrap.controller;

import org.apache.commons.validator.routines.EmailValidator;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.frontbackend.thymeleaf.bootstrap.model.EmailContainer;

@RestController
@RequestMapping("email")
public class EmailRestController {

    @PostMapping
    public ResponseEntity<String> validateEmail(EmailContainer emailContainer) {
        String email = emailContainer.getEmail();
        boolean valid = EmailValidator.getInstance()
                                      .isValid(email);
        return valid ? ResponseEntity.ok(email) : ResponseEntity.badRequest()
                                                                .body(email);
    }
}

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

The presentation layour contains single view index.html with the following structure:

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 Toast</title>

    <link th:rel="stylesheet" th:href="@{/assets/izitoast/css/iziToast.css} "/>
    <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 Toast</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">
        <div class="col-md-4 mt-5">
            <form method="post" action="email">

                <div class="form-group">
                    <label for="email">Email:</label>
                    <input id="email" placeholder="Email address" autocomplete="off" class="form-control"/>
                </div>

                <button class="btn btn-primary" type="submit">Validate email</button>
            </form>
        </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 th:src="@{/assets/izitoast/js/iziToast.min.js}"></script>

<script>
    $('button[type="submit"]').click(function (event) {
        event.preventDefault();

        $.post("/email", {'email': $('#email').val()}, function () {
            iziToast.show({
                title: 'Success',
                message: 'Provided email address is valid',
                position: 'topRight',
                timeout: 500000,
                color: 'green'
            });

        }).fail(function () {
            iziToast.show({
                title: 'Fail',
                message: 'This is not a valid email address',
                position: 'topRight',
                color: 'red'
            });
        });
    });
</script>

</body>
</html>

We choose an external library for creating Toast notifications: https://github.com/marcelodolza/iziToast.

The following JavaScript code was used to call /email endpoint with entered email address. The toast notifications shows if an email is valid or not:

$('button[type="submit"]').click(function(event) {
    event.preventDefault();

    $.post("/email", {
        'email': $('#email').val()
    }, function() {
        iziToast.show({
            title: 'Success',
            message: 'Provided email address is valid',
            position: 'topRight',
            timeout: 500000,
            color: 'green'
        });

    }).fail(function() {
        iziToast.show({
            title: 'Fail',
            message: 'This is not a valid email address',
            position: 'topRight',
            color: 'red'
        });
    });
});

5. The output

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

Thymeleaf bootstrap toast

6. Conclusion

In this article, we presented the Thymeleaf Toast component. This kind of notifications is mainly used to show if some asynchronous function finished successfully or with errors. It is also a very nice way to notify the user of what is going on on the backend.

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

{{ message }}

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