Table of Contents
Introduction
In this example I am going to show you how to build multisteps form using Spring Boot thymeleaf jquery. Multi steps form is required when users have to fill a significant number of fields in the online registration form or some kind of registration or kyc related form and the form goes long at the bottom if the form is not broken into multi-steps.
Having multi-steps form provides some benefits from the end users’ perspective, because input fields on the form are grouped according to their nature of information; another thing is end users do not need to scroll down to fill up the form. So, the users just fill up the information and goes to the next step and fill up the information until the last step. This way also it hides the input fields in the next step.
Related Posts:
Prerequisites
Java 1.8+ (11 – 12), Spring Boot 2.6.7, MySQL 8.0.26, jQuery 3.6.0, Maven 3.8.5, Hibernate Validator 8.0.0.Alpha3
Project Setup
The following pom.xml file can be used to build the Spring Boot project.
<?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>
<groupId>com.roytuts</groupId>
<artifactId>spring-boot-jquery-multi-step-form</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.7</version>
</parent>
<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.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>-->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>8.0.0.Alpha3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
MySQL Table
The following MySQL table can be used for this example. The table is created under roytuts database.
CREATE TABLE `users` (
`id` INT COLLATE utf8mb4_unicode_ci NOT NULL AUTO_INCREMENT,
`name` VARCHAR(100) COLLATE utf8mb4_unicode_ci NOT NULL,
`password` VARCHAR(255) COLLATE utf8mb4_unicode_ci NULL,
`email` VARCHAR(100)COLLATE utf8mb4_unicode_ci NULL,
`phone` INT COLLATE utf8mb4_unicode_ci NOT NULL,
`gender` VARCHAR(6) COLLATE utf8mb4_unicode_ci NOT NULL,
`dob` VARCHAR(10) COLLATE utf8mb4_unicode_ci NOT NULL,
`address` VARCHAR(255) COLLATE utf8mb4_unicode_ci NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
DataSource Config
The following content is written into src/main/resources/application.properties file with the standard naming conventions, so you don’t need to create a datasource manually and Spring will create one for you.
spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/roytuts
spring.datasource.username=root
spring.datasource.password=root
Entity Class
I am using Spring Data JPA to query the database and get benefits of Spring framework to use the built-in API for performing database activities. The following entity class maps to the corresponding table. I have also used validation for each field using Hibernate validator.
package com.roytuts.spring.boot.jquery.multistep.form;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.validation.constraints.Email;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import org.springframework.format.annotation.DateTimeFormat;
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@NotEmpty
@Column
private String name;
@Email
@NotBlank
@Column
private String email;
@NotNull
@Column
private String password;
@Min(10)
@Column
private Long phone;
@NotEmpty
@Column
private String gender;
@NotEmpty
@Column
@DateTimeFormat(pattern = "dd-MM-yyyy")
private String dob;
@NotNull
@Column
private String address;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Long getPhone() {
return phone;
}
public void setPhone(Long phone) {
this.phone = phone;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getDob() {
return dob;
}
public void setDob(String dob) {
this.dob = dob;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
Repository Interface
The repository interface (JpaRepository or CrudRepository) provides built-in methods for querying database.
package com.roytuts.spring.boot.jquery.multistep.form;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User, Integer> {
}
Service Class
The service class is supposed to handle the business logic for the application.
package com.roytuts.spring.boot.jquery.multistep.form;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public void save(final User user) {
userRepository.save(user);
}
}
Spring Web Controller
The web controller class that handles requests and responses for the clients or end users.
package com.roytuts.spring.boot.jquery.multistep.form;
import javax.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class AppController {
@Autowired
private UserService userService;
@GetMapping("/")
public String showUserForm(User user) {
return "index";
}
@PostMapping("/")
public String validateUserForm(@Valid User user, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
return "index";
}
userService.save(user);
return "redirect:/success";
}
@ResponseBody
@GetMapping("/success")
public String success() {
return "User data successfully saved!";
}
}
Spring Boot Main Class
A class is having a method method with @SpringBootApplication
annotation will deploy the app into embedded Tomcat server.
package com.roytuts.spring.boot.jquery.multistep.form;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
//@EntityScan(basePackages = "com.roytuts.spring.boot.jquery.multistep.form")
//@EnableJpaRepositories(basePackages = "com.roytuts.spring.boot.jquery.multistep.form")
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
UI (User Interface)
The standard directory for keeping the static resources is src/main/resources/templates folder from where Spring Boot application will pick up the static content and serve. The following content is written into the file index.html. I am using Spring’s Thymeleaf framework for the UI purpose.
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Spring Boot Multi Step Form</title>
<style>
body {
font-family: tahoma;
font-size: 12px;
}
#signup-step {
margin: auto;
padding: 0;
width: 53%
}
#signup-step li {
list-style: none;
float: left;
padding: 5px 10px;
border-top: #004C9C 1px solid;
border-left: #004C9C 1px solid;
border-right: #004C9C 1px solid;
border-radius: 5px 5px 0 0;
}
.active {
color: #FFF;
}
#signup-step li.active {
background-color: #004C9C;
}
#signup-form {
clear: both;
border: 1px #004C9C solid;
padding: 20px;
width: 50%;
margin: auto;
}
.demoInputBox {
padding: 10px;
border: #CDCDCD 1px solid;
border-radius: 4px;
background-color: #FFF;
width: 50%;
}
.signup-error {
color: #FF0000;
padding-left: 15px;
}
.message {
color: #00FF00;
font-weight: bold;
width: 100%;
padding: 10;
}
.btnAction {
padding: 5px 10px;
background-color: #F00;
border: 0;
color: #FFF;
cursor: pointer;
margin-top: 15px;
}
label {
line-height: 35px;
}
</style>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"
integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4="
crossorigin="anonymous"></script>
<script>
function validate() {
var output = true;
$(".signup-error").html('');
if ($("#personal-field").css('display') != 'none') {
if (!($("#name").val())) {
output = false;
$("#name-error").html("Name required!");
}
if (!($("#dob").val())) {
output = false;
$("#dob-error").html("Date of Birth required!");
}
}
if ($("#password-field").css('display') != 'none') {
if (!($("#user-password").val())) {
output = false;
$("#password-error").html("Password required!");
}
if (!($("#confirm-password").val())) {
output = false;
$("#confirm-password-error").html("Confirm password required!");
}
if ($("#user-password").val() != $("#confirm-password").val()) {
output = false;
$("#confirm-password-error").html("Password not matched!");
}
}
if ($("#contact-field").css('display') != 'none') {
if (!($("#phone").val())) {
output = false;
$("#phone-error").html("Phone required!");
}
if (!($("#email").val())) {
output = false;
$("#email-error").html("Email required!");
}
if (!$("#email").val().match(/^([\w-\.]+@([\w-]+\.)+[\w-]{2,4})?$/)) {
$("#email-error").html("Invalid Email!");
output = false;
}
if (!($("#address").val())) {
output = false;
$("#address-error").html("Address required!");
}
}
return output;
}
$(document).ready(function() {
$("#next").click(function() {
var output = validate();
if (output === true) {
var current = $(".active");
var next = $(".active").next("li");
if (next.length > 0) {
$("#" + current.attr("id") + "-field").hide();
$("#" + next.attr("id") + "-field").show();
$("#back").show();
$("#finish").hide();
$(".active").removeClass("active");
next.addClass("active");
if ($(".active").attr("id") == $("li").last().attr("id")) {
$("#next").hide();
$("#finish").show();
}
}
}
});
$("#back").click(function() {
var current = $(".active");
var prev = $(".active").prev("li");
if (prev.length > 0) {
$("#" + current.attr("id") + "-field").hide();
$("#" + prev.attr("id") + "-field").show();
$("#next").show();
$("#finish").hide();
$(".active").removeClass("active");
prev.addClass("active");
if ($(".active").attr("id") == $("li").first().attr("id")) {
$("#back").hide();
}
}
});
$("input#finish").click(function(e) {
var output = validate();
var current = $(".active");
if (output === true) {
return true;
} else {
//prevent refresh
e.preventDefault();
$("#" + current.attr("id") + "-field").show();
$("#back").show();
$("#finish").show();
}
});
});
</script>
</head>
<body>
<ul id="signup-step">
<li id="personal" class="active">Personal Detail</li>
<li id="password">Password</li>
<li id="contact">Contact</li>
</ul>
<form name='frmRegistration' id='signup-form' th:action="@{/}" th:object="${user}" method="post">
<div id="personal-field">
<label>Name</label><span id="name-error" class="signup-error"></span>
<div>
<input type="text" name="name" id="name" th:field="*{name}" class="demoInputBox" />
</div>
<label>Date of Birth</label><span id="dob-error" class="signup-error"></span>
<div>
<input type="text" name="dob" id="dob" th:field="*{dob}" class="demoInputBox" />
</div>
<label>Gender</label>
<div>
<select name="gender" id="gender" th:field="*{gender}" class="demoInputBox">
<option value="male">Male</option>
<option value="female">Female</option>
</select>
</div>
</div>
<div id="password-field" style="display: none;">
<label>Enter Password</label><span id="password-error"
class="signup-error"></span>
<div>
<input type="password" name="password" th:field="*{password}" id="user-password"
class="demoInputBox" />
</div>
<label>Re-enter Password</label><span id="confirm-password-error"
class="signup-error"></span>
<div>
<input type="password" name="confirm-password" id="confirm-password"
class="demoInputBox" />
</div>
</div>
<div id="contact-field" style="display: none;">
<label>Phone</label><span id="phone-error" class="signup-error"></span>
<div>
<input type="text" name="phone" th:field="*{phone}" id="phone" class="demoInputBox" />
</div>
<label>Email</label><span id="email-error" class="signup-error"></span>
<div>
<input type="text" name="email" th:field="*{email}" id="email" class="demoInputBox" />
</div>
<label>Address</label><span id="address-error" class="signup-error"></span>
<div>
<textarea name="address" th:field="*{address}" id="address" class="demoInputBox" rows="5"
cols="50"></textarea>
</div>
</div>
<div>
<input class="btnAction" type="button" name="back" id="back"
value="Back" style="display: none;"> <input
class="btnAction" type="button" name="next" id="next" value="Next">
<input class="btnAction" type="submit" name="finish" id="finish"
value="Finish" style="display: none;">
</div>
</form>
</body>
</html>
Testing Multi Steps Form
Now you can run the main class to deploy the application and accessing the URL at http://localhost:8080 will show you the following page in the browser:
You cannot go to the next step until you fill up all required fields. Once you reach the last step and fill all inputs, you can save the information. The information will be saved to the table users.