Spring Boot + Bootstrap + Thymeleaf Input Mask

July 06, 2020 No comments Spring Boot Bootstrap Thymeleaf Input Mask

Spring Boot + Bootstrap + Thymeleaf Input Mask

1. Introduction

In this article, we are going to present Thymeleaf Input Mask components embedded in a Spring Boot application. The application will use Bootstrap framework and Inputmask library created by Robin Herbots. It is the best available library for creating input mask components.

For more information about Thymeleaf check below links:
Thymeleaf configuration with Spring Boot
Working with Forms in Thymeleaf

2. Dependencies

2.1. Maven dependencies

The application use three main Maven dependencies:

2.2. Front-End libraries

Front-end layer use two libraries:

  • bootstrap - GUI framework for responsive layouts,
  • jquery - JavaScript framework,
  • Inputmask - a JavaScript library which creates an input mask.

Maven pom.xml file have 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-input-mask</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>
    </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. Model, Controller and Main Application class

The application will handle all GET and POST requests to the root context. In that purpose we created IndexController class that handles that requests and returns processed data.

The IndexController have the following structure:

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.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

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

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

    @GetMapping
    public String main(Model model) {
        model.addAttribute("input", new Input());
        return "index";
    }

    @PostMapping
    public String save(Input input, Model model) {
        model.addAttribute("input", input);
        return "saved";
    }
}

In the model layer, we have a single class Input with several fields which we use to test different mask configurations.

package com.frontbackend.thymeleaf.bootstrap.model;

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

@Setter
@Getter
@NoArgsConstructor
public class Input {

    private String time12h;
    private String time24h;
    private String email;
    private String creditCardNumber;
    private String currency;
    private String ipAddress;
    private String decimal;
}

Spring Boot main class responsible for running the application has the following structure:

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

Presentation layer contains two Thymeleaf templates:

  • index.html - main page served for the root context,
  • saved.html - template for presenting submitted values from the previous view.

The index.html 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 Input Mask</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 Input Mask</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-sm-10 mt-5">
            <form method="post" th:object="${input}">
                <div class="form-group row">
                    <label for="email" class="col-sm-4 col-form-label">Email</label>
                    <div class="col-sm-8">
                        <input type="text" class="form-control" id="email" data-inputmask="'alias': 'email'"
                               autocomplete="off" th:field="*{email}"/>
                    </div>
                </div>

                <div class="form-group row">
                    <label for="time12h" class="col-sm-4 col-form-label">Time (12h)</label>
                    <div class="col-sm-8">
                        <input type="text" class="form-control" id="time12h" data-inputmask-alias="datetime"
                               data-inputmask-inputformat="hh:MM" data-inputmask-placeholder="hh:mm"
                               autocomplete="off" th:field="*{time12h}"/>
                    </div>
                </div>

                <div class="form-group row">
                    <label for="time24h" class="col-sm-4 col-form-label">Time (24h)</label>
                    <div class="col-sm-8">
                        <input type="text" class="form-control" id="time24h" data-inputmask-alias="datetime"
                               data-inputmask-inputformat="HH:MM" data-inputmask-placeholder="hh:mm"
                               autocomplete="off" th:field="*{time24h}"/>
                    </div>
                </div>

                <div class="form-group row">
                    <label for="currency" class="col-sm-4 col-form-label">Currency</label>
                    <div class="col-sm-8">
                        <input type="text" class="form-control" id="currency"
                               data-inputmask="'alias': 'numeric', 'groupSeparator': ',', 'autoGroup': true, 'digits': 2, 'digitsOptional': false, 'prefix': '€ ', 'placeholder': '0'"
                               autocomplete="off" th:field="*{currency}"/>
                    </div>
                </div>

                <div class="form-group row">
                    <label for="ipaddress" class="col-sm-4 col-form-label">IP Address</label>
                    <div class="col-sm-8">
                        <input type="text" class="form-control" id="ipaddress" data-inputmask="'alias': 'ip'"
                               autocomplete="off" th:field="*{ipAddress}"/>
                    </div>
                </div>

                <div class="form-group row">
                    <label for="creditCard" class="col-sm-4 col-form-label">Credit Card Number</label>
                    <div class="col-sm-8">
                        <input type="text" class="form-control" id="creditCard"
                               data-inputmask="'mask': '9999 - 9999 - 9999 - 9999'"
                               autocomplete="off" th:field="*{creditCardNumber}"/>
                    </div>
                </div>

                <button class="btn btn-primary" type="submit">Submit form</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/inputmask/jquery.inputmask.min.js}"></script>

<script>
    $(":input").inputmask();
</script>

</body>
</html>

To create input mask components we used https://github.com/RobinHerbots/Inputmask library available under the MIT licence. The library allows us to configure masks using simple HTML attributes. We just have to initialize it in jQuery document ready function or at the bottom of our website:

$(":input").inputmask();

The saved.html 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 Input Mask</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 Input Mask</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-sm-12 mt-5">
            <p>
                <label>Email: </label>
                <strong th:text="${input.email}"></strong>
            </p>

            <p>
                <label>Time (12h): </label>
                <strong th:text="${input.time12h}"></strong>
            </p>

            <p>
                <label>Time (24h): </label>
                <strong th:text="${input.time24h}"></strong>
            </p>

            <p>
                <label>Currency: </label>
                <strong th:text="${input.currency}"></strong>
            </p>

            <p>
                <label>IP Address: </label>
                <strong th:text="${input.ipAddress}"></strong>
            </p>

            <p>
                <label>Credit card number: </label>
                <strong th:text="${input.creditCardNumber}"></strong>
            </p>
        </div>
    </div>

    <a th:href="@{/}" class="btn btn-primary">Go back</a>
</div>

</body>
</html>

5. The output

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

Thymeleaf bootstrap time picker

6. Conclusion

In this article, we presented how to create Thymeleaf Input Mask components using the Spring Boot application server and the Thymeleaf template engine.

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

{{ message }}

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