1. Introduction
In this article, we are going to present Thymeleaf Autocomplete component embedded in a Spring Boot application. This tutorial example will use the Bootstrap framework and special library - Select2 that allows creating searchable select inputs.
Looking for info how to configure Thymeleaf? Check below links:
Spring Boot with Thymeleaf
Thymeleaf Forms
2. Maven dependencies
The example application using three common dependencies:
The Maven pom.xml
file will 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-autocomplete-input</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, Web Controller, Rest Controller and Application class
The application use three types of classes:
Web Controller
- handle all GET requests, returns rendered website,
Rest Controller
- load searched States
that supply autocomplete component,
Model
- where we defined the main object used in a form, and enum with states.
The base model object Birthplace
(command object) have the following structure:
Copy
package com.frontbackend.thymeleaf.bootstrap.model;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor
public class Birthplace {
private State state;
}
State
enum contains all states from the USA:
Copy
package com.frontbackend.thymeleaf.bootstrap.model;
import lombok.Getter;
public enum State {
AL("Alabama"),
AK("Alaska"),
AZ("Arizona"),
AR("Arkansas"),
CA("California"),
CO("Colorado"),
CT("Connecticut"),
DE("Delaware"),
DC("District of Columbia"),
FL("Florida"),
GA("Georgia"),
HI("Hawaii"),
ID("Idaho"),
IL("Illinois"),
IN("Indiana"),
IA("Iowa"),
KS("Kansas"),
KY("Kentucky"),
LA("Louisiana"),
ME("Maine"),
MT("Montana"),
NE("Nebraska"),
NV("Nevada"),
NH("New Hampshire"),
NJ("New Jersey"),
NM("New Mexico"),
NY("New York"),
NC("North Carolina"),
ND("North Dakota"),
OH("Ohio"),
OK("Oklahoma"),
OR("Oregon"),
MD("Maryland"),
MA("Massachusetts"),
MI("Michigan"),
MN("Minnesota"),
MS("Mississippi"),
MO("Missouri"),
PA("Pennsylvania"),
RI("Rhode Island"),
SC("South Carolina"),
SD("South Dakota"),
TN("Tennessee"),
TX("Texas"),
UT("Utah"),
VT("Vermont"),
VA("Virginia"),
WA("Washington"),
WV("West Virginia"),
WI("Wisconsin"),
WY("Wyoming");
@Getter
String label;
State(String label) {
this.label = label;
}
}
StateItem
class will be used to load options for autocomplete component:
Copy
package com.frontbackend.thymeleaf.bootstrap.model;
import lombok.Builder;
import lombok.Getter;
@Getter
@Builder
public class StateItem {
private State id;
private String text;
private String slug;
}
Web controller class have the following structure:
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.Birthplace;
@Controller
@RequestMapping({ "/", "/index" })
public class IndexController {
@GetMapping
public String main(Model model) {
model.addAttribute("birthplace", new Birthplace());
return "index";
}
@PostMapping
public String save(Birthplace birthplace, Model model) {
model.addAttribute("birthplace", birthplace);
return "saved";
}
}
StatesRestController
class is our REST endpoint for supplying autocomplete component:
Copy
package com.frontbackend.thymeleaf.bootstrap.controller;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.frontbackend.thymeleaf.bootstrap.model.State;
import com.frontbackend.thymeleaf.bootstrap.model.StateItem;
@RestController
@RequestMapping("states")
public class StatesRestController {
@GetMapping
public List<StateItem> stateItems(@RequestParam(value = "q", required = false) String query) {
if (StringUtils.isEmpty(query)) {
return Arrays.stream(State.values())
.limit(15)
.map(this::mapToStateItem)
.collect(Collectors.toList());
}
return Arrays.stream(State.values())
.filter(state -> state.getLabel()
.toLowerCase()
.contains(query))
.limit(15)
.map(this::mapToStateItem)
.collect(Collectors.toList());
}
private StateItem mapToStateItem(State state) {
return StateItem.builder()
.id(state)
.text(state.getLabel())
.slug(state.name())
.build();
}
}
Application
start the Spring Boot application server:
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
We defined two Thymeleaf templates in the /resources/templates
directory:
index.html
- to present the main form with autocomplete component,
saved.html
- where we are going to present a selected option that was chosen on the previous page.
The index.html
file will have 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 Autocomplete</title>
<link th:rel="stylesheet" th:href="@{assets/select2-develop/dist/css/select2.css}"/>
<link th:rel="stylesheet" th:href="@{assets/select2-bootstrap4-theme-master/dist/select2-bootstrap4.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 Autocomplete</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" th:object="${birthplace}">
<div class="form-group">
<label for="birthplace">Place of birth</label>
<select id="birthplace" class="form-control select2-single" th:field="*{state}">
<option value="">Search state</option>
</select>
</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/select2-develop/dist/js/select2.full.js}"></script>
<script>
$("#birthplace").select2({
theme: "bootstrap4",
ajax: {
url: '/states',
dataType: 'json',
delay: 250,
processResults: function (response) {
return {
results: response
};
},
cache: true
}
});
</script>
</body>
</html>
In this example application we used Select2 component that supports searching, remote data sets, and infinite scrolling of results.
The save.html
file will have 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 Autocomplete</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 Autocomplete</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">Your place of birth: <strong th:text="${birthplace.state.label}" th:if="${birthplace.state != null}"></strong></h2>
<a th:href="@{/}" class="btn btn-primary">Go back</a>
</div>
</body>
</html>
5. The output
The application starts on default Spring Boot port 8080
and works as on the following GIF:
6. Conclusion
In this article, we presented how to build and handle Thymeleaf Autocomplete components. We used the best in our opinion library for creating searchable select inputs - Select2 . It works on almost all browsers (even IE 8+) and comes with a really nice set of features.
As usual, the code used in this example is available under our GitHub repo .
{{ 'Comments (%count%)' | trans {count:count} }}
{{ 'Comments are closed.' | trans }}