Spring Boot + Bootstrap + Thymeleaf Input Number

May 18, 2020 No comments Spring Boot Bootstrap Thymeleaf Input Number

Spring Boot + Bootstrap + Thymeleaf Input Number

1. Introduction

In this article, we will present the Thymeleaf Input Number component that comes with built-in validation to reject non-numerical entries. This type of component is supported by all modern browsers.

If you are looking for more useful informations about Thymeleaf, please check the following articles:
Thymeleaf Tutorial
Using forms in Thymeleaf

2. Dependencies

2.1. Maven dependencies

To present how the Input Number 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-input-number</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>
        <font-awesome.version>5.11.2</font-awesome.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.webjars</groupId>
            <artifactId>font-awesome</artifactId>
            <version>${font-awesome.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

Let's start with the model layer. The Form object is our base command class that will be attached to the Thymeleaf Form.

package com.frontbackend.thymeleaf.bootstrap.model;

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

@Setter
@Getter
@NoArgsConstructor
public class Form {

    private Long apples;
}

GET and POST requests on the root context are handled by a Controller with the simple 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.Form;

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

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

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

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 layer contains two Thymeleaf templates:

  • index.html - a view that presents the input number component,
  • saved.html - the result view that presents entered value.

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 Input Number</title>

    <link th:rel="stylesheet" th:href="@{webjars/bootstrap/4.0.0-2/css/bootstrap.min.css} "/>
    <link th:rel="stylesheet" th:href="@{webjars/font-awesome/5.11.2/css/all.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 Number</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">
    <form method="post" th:object="${form}">
        <div class="form-group row">
            <div class="col-md-4 mt-5">
                <label for="apples">Number of apples (10-100)</label>
                <input type="number" class="form-control" id="apples" autocomplete="off" th:field="*{apples}"
                       min="10" max="100"/>
            </div>
        </div>

        <button class="btn btn-primary" type="submit">Submit form</button>
    </form>
</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>

</body>
</html>

The saved.html file 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 Number</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 Number</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">
    <h2 class="mt-5">Number of apples: <strong th:text="${form.apples}"></strong></h2>

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

</body>
</html>

5. The output

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

Thymeleaf bootstrap input number

6. Conclusion

In this article, we presented the Thymeleaf Input Number component. All modern browsers provide stepper arrows to let the user increase and decrease the value using their mouse or by simply tapping with a fingertip.

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

{{ message }}

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