1. Introduction
In this article, we are going to present Thymeleaf Input Spinner component. Although HTML 5 provides a dedicated input spinner (with type="number"
attribute) we decided to use the custom one created with JavaScript library bootstrap-input-spinner . Why? because it is responsive and looks great.
You can find more information about Thymeleaf and Forms in the following links:
Thymeleaf Tutorial
Working with Forms in Thymeleaf
2. Dependencies
2.1. Maven dependencies
To show how Input Spinner works, we used a sample Spring Boot application created as a Maven project using three main dependencies:
2.2. Front-End libraries
bootstrap - a frontend framework for creating responsive websites.
bootstrap-input-spinner - a JavaScript library for creating responsive input spinner components for websites that uses Bootstrap framework.
Maven pom.xml
file have the following structure:
Copy
<?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-spinner</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
The sample application takes three parameters as input: temp, wind and humidity using spinner component.
The main object in the model layer(Thymeleaf command object) has the following structure:
Copy
package com.frontbackend.thymeleaf.bootstrap.model;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Setter
@Getter
@NoArgsConstructor
public class Weather {
private float temp;
private float wind;
private float humidity;
}
GET requests to the root context are handled by IndexController
class:
Copy
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.Weather;
@Controller
@RequestMapping({ "/", "/index" })
public class IndexController {
@GetMapping
public String main(Model model) {
model.addAttribute("weather", new Weather());
return "index";
}
@PostMapping
public String save(Weather weather, Model model) {
model.addAttribute("weather", weather);
return "saved";
}
}
Main Spring Boot application class has the following structure:
Copy
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 two Thymeleaf templats:
index.html - on this view user can enter wheather parameters using spinner components,
saved.html - this view is responsibe for presenting the submitted values.
The index.html
template has the following structure:
Copy
<!DOCTYPE HTML>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title>Spring Boot Thymeleaf Application - Bootstrap Input Spinner</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 Spinner</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-lg-5 mt-5">
<form method="post" th:object="${weather}">
<div class="form-group">
<label for="temp">Temp</label>
<input data-suffix="°C" value="0" min="-50" max="50" type="number" class="form-control" id="temp"
autocomplete="off" th:field="*{temp}"/>
</div>
<div class="form-group">
<label for="wind">wind</label>
<input data-suffix="m/s" value="0" min="0" max="50" type="number" class="form-control" id="wind"
autocomplete="off" th:field="*{wind}" step="0.5" data-decimals="2"/>
</div>
<div class="form-group">
<label for="humidity">Humidity</label>
<input data-suffix="%" value="0" min="0" max="100" type="number" class="form-control" id="humidity"
autocomplete="off" th:field="*{humidity}" step="0.5" data-decimals="2"/>
</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/bootstrap-input-spinner/bootstrap-input-spinner.js}"></script>
<script>
var $input = $("input[type='number']");
$input.inputSpinner();
</script>
</body>
</html>
We used an external JavaScript plugin for creating responsive Input Spinners : https://github.com/shaack/bootstrap-input-spinner .
To initialize bootstrap-input-spinner library, and attach it to all inputs with type = number
, we use the following JS code:
Copy
var $input = $("input[type='number']");
$input.inputSpinner();
The saved.html
template has the following structure:
Copy
<!DOCTYPE HTML>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title>Spring Boot Thymeleaf Application - Bootstrap Input Spinner</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 Spinner</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>Temp: </label><br/>
<strong th:text="${weather.temp}"></strong> <span>°C</span>
</p>
<p>
<label>Wind: </label><br/>
<strong th:text="${weather.wind}"></strong> <span>m/s</span>
</p>
<p>
<label>Humidity: </label><br/>
<strong th:text="${weather.humidity}"></strong> <span>%</span>
</p>
<a th:href="@{/}" class="btn btn-primary">Go back</a>
</div>
</div>
</div>
</body>
</html>
5. The output
The running application is available under http://locahost:8080
URL and presents the following functionality:
6. Conclusion
In this article, we presented Thymeleaf Input Spinner component. Although browsers support spinner input with type=number
we choose an external library that allows us to create awesome, responsive and bootstrap-style components.
As usual, the code used in this article is available under our GitHub repository .
{{ 'Comments (%count%)' | trans {count:count} }}
{{ 'Comments are closed.' | trans }}