Spring Boot + Bootstrap + Thymeleaf Slider

April 18, 2020 No comments Spring Boot Bootstrap Thymeleaf Slider

Spring Boot + Bootstrap + Thymeleaf Slider

1. Introduction

In this article, we are going to present Thymeleaf Slider embedded in a Spring Boot application. In the presentation layer of sample application, we will use the Bootstrap framework for responsive design and bootstrap-slider for custom slider components.

Need more information about Thymeleaf and Spring Boot? check below links:
Thymeleaf Tutorial
Spring Boot with Thymeleaf
Forms in Thymeleaf

2. Maven dependencies

Project requires three common dependencies:

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-slider</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

The main purpose of this application is to save programming skills using sliders and Thymeleaf. The presentation layer contains single controller - IndexController class that handles GET request to the root context and POST request with provided skills 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.ProgrammingSkills;

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

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

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

In the model layer we used a single POJO object that will store skills for several programming languages:

package com.frontbackend.thymeleaf.bootstrap.model;

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

@Setter
@Getter
@NoArgsConstructor
public class ProgrammingSkills {

    private int java;
    private int css;
    private int angular;
    private int react;
    private int python;
    private int go;

}

This is our base object used in the Thymeleaf form (command object).

4. Template

The sample application comes with two HTML files.

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 Slider</title>

    <link th:rel="stylesheet" th:href="@{assets/bootstrap-slider/css/bootstrap-slider.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 Slider</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">
            <h2 class="mt-5">Programming skills</h2>

            <form method="post" th:object="${skills}">
                <div class="ml-2">
                    <div class="form-group mt-3">
                        <input id="java" type="text" th:field="*{java}" data-slider-min="1" data-slider-max="10"
                               data-slider-step="1"
                               data-slider-value="1" class="skill"/>
                        <span class="ml-2">JAVA: <strong id="javaVal">1</strong></span>
                    </div>

                    <div class="form-group mt-3">
                        <input id="css" type="text" th:field="*{css}" data-slider-min="1" data-slider-max="10"
                               data-slider-step="1"
                               data-slider-value="1" class="skill"/>
                        <span class="ml-2">CSS: <strong id="cssVal">1</strong></span>
                    </div>

                    <div class="form-group mt-3">
                        <input id="angular" type="text" th:field="*{angular}" data-slider-min="1" data-slider-max="10"
                               data-slider-step="1"
                               data-slider-value="1" class="skill"/>
                        <span class="ml-2">Angular: <strong id="angularVal">1</strong></span>
                    </div>

                    <div class="form-group mt-3">
                        <input id="python" type="text" th:field="*{python}" data-slider-min="1" data-slider-max="10"
                               data-slider-step="1"
                               data-slider-value="1" class="skill"/>
                        <span class="ml-2">Python: <strong id="pythonVal">1</strong></span>
                    </div>

                    <div class="form-group mt-3">
                        <input id="react" type="text" th:field="*{react}" data-slider-min="1" data-slider-max="10"
                               data-slider-step="1"
                               data-slider-value="1" class="skill"/>
                        <span class="ml-2">React: <strong id="reactVal">1</strong></span>
                    </div>

                    <div class="form-group mt-3">
                        <input id="go" type="text" th:field="*{go}" data-slider-min="1" data-slider-max="10"
                               data-slider-step="1"
                               data-slider-value="1" class="skill"/>
                        <span class="ml-2">Go: <strong id="goVal">1</strong></span>
                    </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:rel="stylesheet" th:src="@{assets/bootstrap-slider/bootstrap-slider.js}"></script>

<script>
    $(".skill").slider();
    $(".skill").on("slide", function (slideEvt) {
        $("#" + slideEvt.target.id + "Val").text(slideEvt.value);
    });
    $(".skill").on("change", function (slideEvt) {
        $("#" + slideEvt.target.id + "Val").text(slideEvt.value.newValue);
    });
</script>

</body>
</html>

We used external library for making custom sliders - bootstrap-slider. This template present sliders which we can use to enter our skills level from 1 to 10.

The saved.html presents salected values:

<!DOCTYPE HTML>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8"/>
    <title>Spring Boot Thymeleaf Application - Bootstrap Slider</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 Slider</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">Programming skills</h2>

    <p>JAVA: <strong th:text="${skills.java}"></strong></p>
    <p>CSS: <strong th:text="${skills.css}"></strong></p>
    <p>Angular: <strong th:text="${skills.angular}"></strong></p>
    <p>Python: <strong th:text="${skills.python}"></strong></p>
    <p>React: <strong th:text="${skills.react}"></strong></p>
    <p>Go: <strong th:text="${skills.go}"></strong></p>

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

</body>
</html>

5. The output

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

Thymeleaf bootstrap slider

6. Conclusion

In this article, we presented how to create Thymeleaf Slider in a Spring Boot application. Sliders are components used very often in an HTML form, especially on mobile devices. That kind of component gives users a great experience - it is easier to move the slider than enter the value from the keyboard.

{{ message }}

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