Spring Boot + Bootstrap + Thymeleaf Radio Button

January 04, 2020 No comments Thymeleaf Spring Boot Radio Button

1. Introduction

In this article, we are going to present Thymeleaf Radio Button component embedded in a Spring Boot application. We will use the Bootstrap framework to create a responsive layout for our example website.

If you are looking for some more information about how to configure Thymeleaf for Spring Boot and how to work with forms, check below links:
Spring Boot with Thymeleaf
Thymeleaf Forms

2. Maven dependencies

This example application use several dependecies:

  • lombok - common methods generator for POJO classes,
  • bootstrap webjar - for responsive layout,
  • springframework - the Spring Boot application server.

The Maven pom.xml file will 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-radio-button</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 Application class

We defined endpoints to handle GET and POST requests in IndexController class. The base model object Car contains an enum field CarModel and an additional int field that will store the selected option. We will use Car instance in our main form.

The base model object (command object) use in this example application have the following structure:

package com.frontbackend.thymeleaf.bootstrap.model;

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

@Setter
@Getter
@NoArgsConstructor
public class Car {

    private CarModel carModel;
    private int option;

}

Enum that contins available car models:

package com.frontbackend.thymeleaf.bootstrap.model;

public enum CarModel {

    VW,
    SUZUKI,
    DODGE,
    VOLVO

}

Web controller class have the following structure:

package com.frontbackend.thymeleaf.bootstrap.controller;

import com.frontbackend.thymeleaf.bootstrap.model.Car;
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;

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

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

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

The main Spring Boot application class have 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

We defined two Thymeleaf templates in the /resources/templates directory:

  • index.html - to present the main form with radio buttons,
  • saved.html - where we are going to present selected options from the previous view.

The index.html file will have 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 Radio Buttons</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 Radio Buttons</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="${car}" class="mt-5">
        <div class="form-group">
            <label>Model</label>
            <div th:each="model : ${T(com.frontbackend.thymeleaf.bootstrap.model.CarModel).values()}">
                <div class="custom-control custom-radio custom-control-inline">
                    <input type="radio" th:field="*{carModel}" th:value="${model}" class="custom-control-input">
                    <label class="custom-control-label" th:for="${#ids.prev('carModel')}" th:text="${model}">model</label>
                </div>
            </div>
        </div>
        <div class="form-group">
            <div class="custom-control custom-radio custom-control-inline">
                <input type="radio" id="option1" name="option1" class="custom-control-input" value="1" th:field="*{option}">
                <label class="custom-control-label" for="option1">Option1</label>
            </div>
            <div class="custom-control custom-radio custom-control-inline">
                <input type="radio" id="option2" name="option2" class="custom-control-input" value="2" th:field="*{option}">
                <label class="custom-control-label" for="option2">Option2</label>
            </div>
            <div class="custom-control custom-radio custom-control-inline">
                <input type="radio" id="option3" name="option3" class="custom-control-input" value="3" th:field="*{option}">
                <label class="custom-control-label" for="option3">Option3</label>
            </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>

Note that we use special T expression ${T(com.frontbackend.thymeleaf.bootstrap.model.CarModel).values()}" to iterate over available enum values in Thymeleaf template and present radio buttons with model to choose.

The save.html file will have 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 Radio Buttons</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 Radio Buttons</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="mt-5">Model: <strong th:text="${car.carModel}"></strong></div>
    <div>Option: <strong th:text="${car.option}"></strong></div>

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

</body>
</html>

5. The output

Started application is available under http://locahost:8080 URL:

Thymeleaf bootstrap radio buttons

6. Conclusion

In this article, we showcased how to use Thymeleaf Radio Buttons in a Spring Boot application. Thymeleaf is very flexible, it allows us to attach radio buttons with such types as: String, int, long or enum.

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

{{ message }}

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