1. Introduction
In this article, we are going to present Thymeleaf Price Range Slider embedded in a Spring Boot application. We will use the Bootstrap framework to create a responsive website and bootstrap-slider library for the awesome slider.
Looking for more information about Thymeleaf and Spring Boot? check below links:
Spring Boot with Thymeleaf
Working with forms in Thymeleaf
2. Maven dependencies
This example Spring Boot application will use two dependencies:
Our 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-price-range-slider</artifactId>
<properties>
<bootstrap.version>4.0.0-2</bootstrap.version>
<webjars-locator.version>0.30</webjars-locator.version>
<font-awesome.version>5.11.2</font-awesome.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.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 Application class
To present how to create and handle Price Range Slider in Thymeleaf we used two controllers:
IndexController - handle GET request (to the root context /
) that returns main website and POST request that will process input price range,
ProductController - this controller will return a rendered list of products filtered by the selected price range.
The IndexController
have the following structure:
Copy
package com.frontbackend.thymeleaf.bootstrap.controller;
import org.springframework.beans.factory.annotation.Autowired;
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.PriceRange;
import com.frontbackend.thymeleaf.bootstrap.service.ProductService;
@Controller
@RequestMapping({ "/", "/index" })
public class IndexController {
private final ProductService productService;
@Autowired
public IndexController(ProductService productService) {
this.productService = productService;
}
@GetMapping
public String main(Model model) {
model.addAttribute("priceRange", new PriceRange(5, 100));
model.addAttribute("products", productService.getMockedProducts());
return "index";
}
@PostMapping
public String save(PriceRange priceRange, Model model) {
model.addAttribute("range", priceRange);
return "saved";
}
}
And the ProductController
has the following structure:
Copy
package com.frontbackend.thymeleaf.bootstrap.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import com.frontbackend.thymeleaf.bootstrap.model.PriceRange;
import com.frontbackend.thymeleaf.bootstrap.service.ProductService;
@Controller
@RequestMapping("/products")
public class ProductController {
private final ProductService productService;
@Autowired
public ProductController(ProductService productService) {
this.productService = productService;
}
@GetMapping
public String filterProducts(PriceRange priceRange, Model model) {
model.addAttribute("products", productService.filterProducts(priceRange.getMin(), priceRange.getMax()));
return "products";
}
}
The ProductService
will return filtered products stored in mockedProducts.json
file:
Copy
package com.frontbackend.thymeleaf.bootstrap.service;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.stereotype.Service;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.frontbackend.thymeleaf.bootstrap.model.Product;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service
public class ProductService {
public List<Product> filterProducts(int min, int max) {
return getMockedProducts().stream()
.filter(product -> product.getPrice() >= min && product.getPrice() <= max)
.collect(Collectors.toList());
}
public List<Product> getMockedProducts() {
ObjectMapper objectMapper = new ObjectMapper();
try {
return objectMapper.readValue(getClass().getClassLoader()
.getResourceAsStream("mockedProducts.json"),
new TypeReference<List<Product>>() {
});
} catch (IOException e) {
log.error(e.getMessage(), e);
}
return Collections.emptyList();
}
}
In the model layer we used two POJO objects:
Copy
package com.frontbackend.thymeleaf.bootstrap.model;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Setter
@Getter
@NoArgsConstructor
public class Product {
private String name;
private String material;
private String brand;
private Double price;
}
We use lombok to generate setters, getters and contructors int our POJO classes.
Copy
package com.frontbackend.thymeleaf.bootstrap.model;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Setter
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class PriceRange {
private int min;
private int max;
}
PriceRange class will be the main class used in our Thymeleaf form (command object).
4. Templates
This example Spring Boot application on the root context path serves a index.html
file which 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 Price Range 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} "/>
<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 Price Range 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">
<form method="post" th:object="${priceRange}">
<div class="form-group mt-5">
<label for="priceRange">Filter products by price</label>
<div class="form-control">
<b class="mr-2">€ 10</b> <input id="priceRange" type="text" class="span2" value=""
data-slider-min="1"
data-slider-max="100" data-slider-step="2"
data-slider-value="[5,100]" data-slider-tooltip="show"/> <b
class="ml-2">€ 100</b>
<input type="hidden" id="rangeMin" th:field="*{min}"/>
<input type="hidden" id="rangeMax" th:field="*{max}"/>
</div>
</div>
<div id="products" class="mb-3">
<div th:replace="products :: list"></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>
$("#priceRange").slider({});
$("#priceRange").on("slideStop", function (stopEvent) {
var range = stopEvent.value;
$("#rangeMin").val(range[0]);
$("#rangeMax").val(range[1]);
$.get("/products?min=" + range[0] + "&max=" + range[1], function (data) {
$("#products").html(data);
});
});
</script>
</body>
</html>
We used bootstrap-slider plugin to make a custom slider use to filter products by specified price range.
products.html
is a special Thymeleaf template that can be used as a fragment
. Fragments in Thymeleaf are a special piece of HTML code that could be included in different Thymeleaf templates. We use it to define a list of products just once and include it where we want to.
The products.html
template will have the following structure:
Copy
<div class="row" th:fragment="list" xmlns:th="http://www.thymeleaf.org">
<div th:each="product, stat : ${products}" class="col-sm-4">
<div class="card mb-3">
<div class="card-body">
<h5 class="card-title" th:text="${product.name}">Product name</h5>
<p class="card-text" th:text="${product.price}">Price</p>
<a href="#" class="btn btn-primary">Buy</a>
</div>
</div>
</div>
</div>
5. The output
Started application is available under http://locahost:8080
URL and presents the following functionality:
6. Conclusion
In this article, we showcased how to create Thymeleaf Price Range Slider in a Spring Boot application.
As usual, the full code used in this example is available under our GitHub Repository .
{{ 'Comments (%count%)' | trans {count:count} }}
{{ 'Comments are closed.' | trans }}