Building Dynamic Web Applications with Thymeleaf and Spring Boot

Introduction

Welcome to our comprehensive guide on using Thymeleaf with Spring Boot. Thymeleaf is a modern server-side java template engine for web and standalone environments. It is especially well suited for rendering dynamic HTML content. In this blog, we will explore how to set up thymeleaf with Spring Boot, create templates, handle forms, and utilize various Thymeleaf attributes for powerful template processing.

Creating your first thymeleaf template

Basic structure of a thymeleaf template

Thymeleaf templates are essentially HTML files that include special thymeleaf attribute to process dynamic data. Create HTML file in `src/main/resource/templates`:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Welcome</title>
</head>
<body>
    <h1 th:text="'Hello, ' + ${name} + '!'">Hello, World!</h1>
</body>
</html>

Create a simple spring boot controller

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class GreetingController {

    @GetMapping("/greeting")
    public String greeting(Model model) {
        model.addAttribute("name", "World");
        return "greeting";
    }
}

Thymeleaf attributes

Thymeleaf provides several attributes to control the template processing

  1. th:text
    • Sets the text content of an element
    • Example: `<p>th:text=”‘Hello ‘ + ${name} “</p>`
  2. th:object
    • Binds an object to a form or element for easier access to its properties
    • Example: <form th:object=”${user}”></form>
  3. th:if
    • Conditional render the element base on the expression
    • <p th:if=”${user.isAdmin()}>Wellcome, admin</p>
  4. th:unless
    • Conditional render the element if the expression is false
    • <p th:unless=”${user.isAdmin()}”>Hi, Non admin</p>
  5. th:switch, th:case
    • Conditional render the element

  6. th:each
    • Iterate through the collections
    • Example: <p th:each=”item : ${listItem}” th:text=”${item}”></>
  7. th:href
    • Set href attribute
    • <a th:href=”@{/product/{id}(id=${id})}>product detail</a>
  8. th:src
    • Sets the src attribute
  9. th:action
    • Sets the action attribute
    • <form th:action=”@{/submit}”></form>
  10. th:value
    • Sets the value attribute
    • <input th:value=${name} />

Create a simple spring boot thymeleaf application

Initialize project

  • Go to https://start.spring.io/
  • Select project settings
  • Add dependencies:
    • Thymeleaf
    • Spring web
  • Generate project and import to your IDE

Create HTML file in `src/main/resource/templates`

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Todo list</title>
    <link rel="stylesheet" type="text/css" th:href="@{/css/styles.css}">
</head>
<script>
    function confirmAction(event, message) {
        if (!confirm(message)) {
            event.preventDefault();
        }
    }
</script>
<body>
<div class="container">
    <h1 th:text="${header}">TODO LIST</h1>
    <p th:text="${description}">Manage your works and tasks</p>
    <div>
        <form th:action="@{/todo}" method="post" th:object="${todo}">
            <div class="input-group">
                <input type="hidden" th:field="*{id}">
                <input class="input" placeholder="Enter your task" th:field="*{todoTask}" />
                <select class="status-select" th:field="*{status}">
                    <option th:each="statusItem : ${statusList}" th:value="${statusItem}" th:text="${statusItem.status}"></option>
                </select>
                <button class="btn" type="submit">Submit</button>
            </div>
        </form>
    </div>
    <ul>
        <li th:each="todoItem : ${todos}">
            <p class="todo-info" th:text="${todoItem.todoTask}"></p>
            <div class="function-group-btn">
                <select class="status-select" disabled>
                    <option th:value="${todoItem.status}" th:text="${todoItem.status.status}"></option>
                </select>
                <a th:href="@{/todo/update/{id}(id=${todoItem.id})}" class="btn func-btn">Update</a>
                <a th:href="@{/todo/delete/{id}(id=${todoItem.id})}"  onclick="confirmAction(event, 'Are you sure you want to delete this task?')"  class="btn func-btn" >Delete</a>
            </div>
        </li>
    </ul>
</div>
</body>
</html>

Create DTO Todo

package com.abc.thymeleaf_example.dto;

import java.util.Arrays;
import java.util.UUID;

public class Todo {
    public enum TodoStatus {
        NEW("new"),
        WORKING("working"),
        DONE("done"),
        CANCEL("cancel");

        private String status;

        TodoStatus(String status) {
            this.status = status;
        }
        public String getStatus() {
            return status;
        }

    }
    private String id;
    private String todoTask;
    private TodoStatus status;


    public String getTodoTask() {
        return todoTask;
    }

    public void setTodoTask(String todoTask) {
        this.todoTask = todoTask;
    }

    public TodoStatus getStatus() {
        return status;
    }

    public void setStatus(TodoStatus status) {
        this.status = status;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }
}

Create Todo service

package com.abc.thymeleaf_example.service;

import com.abc.thymeleaf_example.dto.Todo;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

@Service
public class TodoService {
    private Map<String, Todo> list = new ConcurrentHashMap<>();

    public TodoService () {
        Todo todo = new Todo();
        todo.setId(UUID.randomUUID().toString());
        todo.setTodoTask("test");
        todo.setStatus(Todo.TodoStatus.NEW);
        list.put(todo.getId(), todo);
    }

    public List<Todo> getTodoList () {
        return list.values().stream().toList();
    }

    public List<Todo> insertOrUpdateTodo (Todo todo) {
        list.put(todo.getId(), todo);
        return getTodoList();
    }
    public List<Todo> deleteToDo (String id) {
        list.remove(id);
        return getTodoList();
    }

    public Todo getTodoById (String id) {
        return list.get(id);
    }

}

Create Todo controller

package com.abc.thymeleaf_example.controller;

import com.abc.thymeleaf_example.dto.Todo;
import com.abc.thymeleaf_example.dto.Todo.TodoStatus;
import com.abc.thymeleaf_example.service.TodoService;
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.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;

import java.util.UUID;

@Controller
public class TodoController {

    @Autowired
    private TodoService service;

    @GetMapping("/")
    public String indexPage() {
        return "redirect:/todo";
    }

    @GetMapping("/todo/update/{id}")
    public String updateTodoForm(@PathVariable String id, Model model) {
        Todo todo = service.getTodoById(id);
        model.addAttribute("statusList", TodoStatus.values());
        model.addAttribute("header", "TODO LIST");
        model.addAttribute("description", "Make your life easier");
        model.addAttribute("todos", service.getTodoList());
        model.addAttribute("todo", todo);
        return "index";
    }

    @GetMapping("/todo")
    public String todoPage(Model model) {
        model.addAttribute("statusList", TodoStatus.values());
        model.addAttribute("header", "TODO LIST");
        model.addAttribute("description", "Make your life easier");
        model.addAttribute("todos", service.getTodoList());

        Todo todo = new Todo();
        todo.setId(UUID.randomUUID().toString());
        model.addAttribute("todo", todo);

        return "index";
    }

    @PostMapping("/todo")
    public String addTodo(@ModelAttribute Todo todo) {
        if (todo.getStatus() == null) {
            todo.setStatus(TodoStatus.NEW); // Set default status to NEW
        }
        service.insertOrUpdateTodo(todo);
        return "redirect:/todo";
    }


    @GetMapping("/todo/delete/{id}")
    public String deleteTodo(@PathVariable("id") String id) {
        service.deleteToDo(id);
        return "redirect:/todo";
    }
}

Source code: https://github.com/hongquan080799/thymeleaf-example-todo

Conclusion

In this blog, we’ve covered the basics of setting up Thymeleaf with Spring Boot, creating templates, handling forms, and some advanced features. Thymeleaf is a powerful tool that can help you create dynamic and responsive web applications. Happy coding!

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top