[Java] Let’s create a TODO application in Java 5 Switch the TODO display

4 minute read

Hello, this time by pressing the button TODO of completion ⇄ incomplete we want to implement the ability to switch.

1: Brief description of MVC 2: I want to make a template with Spring Initializr and do Hello world 3: Save temporary data in MySQL -> Get all -> Display on top 4: Posting function implementation 5: Switch the TODO display (now here)

Search for a specific TODO by id

First of all, the flow of processing this time ・Search and get TODO from id ・Switch TODO status by true ⇄ false ・Save TODO Will be.

So let’s do the part to get TODO from id immediately.

java/com/example/todo/TodoService.java


    public TodoEntity findTodoById(Long todoId) {
        Optional<TodoEntity> todoResult = todoRepository.findById(todoId);
        // check for null
        return todoResult.get();
    }

I like this.

The Optional type used here is a type that specifies that the contained type may be null.

// check for null I will leave the part that is written because I will do it in future articles.

This time it is Optional type, It can be changed to TodoEntity type by using Optional.get().

Now you can get TODO by id!

Implementation of logic to switch between complete and incomplete

Next, we will implement the logic that switches the state of TODO.

java/com/example/todo/TodoService.java


    public void switchTodo(Long todoId) {
        TodoEntity todoEntity = findTodoById(todoId);
        todoEntity.setStatus(!todoEntity.isStatus());
        todoRepository.save(todoEntity);
    }

First, call the function above to get TODO. And the status is switched, but the status is opposite to the current status by setting !todoEntity.isStatus(). (This is a common writing style when switching booleans.)

Edit the controller

java/com/example/todo/TodoController.java


    @PatchMapping("/toggle-status/{id}")
    public String switchStatus(@PathVariable("id") Long todoId) {
        todoService.switchTodo(todoId);
        return "redirect:/top";
    }

Let’s write like this.

@PathVariable is an annotation that gets the {id} embedded in the URL.

Front side editing

<!DOCTYPE html>
<html lang="ja" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>hello</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JwR1T2JZwR1T2JJR1 ="anonymous">
</head>
<body>

    <!-- Post form -->
    <div class=" w-75 h-auto my-1 mx-auto pt-5">
        <p class="pl-5">Create new ToDo</p>
        <form th:action="@{/register}" th:object="${ToDoForm}" method="POST" class="container d-flex w-auto my-0 mx-auto">
            <div class="w-100">
                <label class="row">
                    <span class="col-2 text-center">ToDo name</span>
                    <input type="text" name="title" placeholder="Enter ToDo within 30 characters" class="col-9">
                </label>
                <label class="row my-0">
                    <span class="col-2 text-center">Due date</span>
                    <input type="date" id="date" name="deadline" class="col-9 my-0">
                </label>
            </div>
            <button class="btn btn-primary w-25 col-2 mr-3" type="submit">Add ToDo</button>
        </form>
    </div>
    <div th:each="todo: ${todoList}" class=" w-75 h-25 my-1 mx-auto pt-5">
        <div class="container">
            <div class="row">
                <div class="col-5 pl-5">
                    <p th:text="${todo.title}" class="mb-1"></p>
                    <p class="mb-1"> Deadline: <span th:text="${todo.deadline}"></span></p>
                    <p class="mb-1"> Creation date: <span th:text="${todo.createTime}"></span></p>
                </div>
                <div class="col-2 d-flex justify-content-start align-items-center px-0">
                    <a class="h-100 w-75 btn btn-info pt-4">
                        Edit
                    </a>
                </div>
<!-- ~ ~ ~ ~ ~ ~ Contents to be added this time ~ ~ ~ ~ ~ -->
                <div th:if="${todo.status}" class="col-3 d-flex px-0">
                    <form th:action="@{/toggle-status/{id}(id=${todo.id})}" method="post" class="w-100 container d-flex my-0 mx-auto p-0 mr-2">
                        <input type="hidden" name="_method" value="patch">
                        <button type="submit" class="h-100 w-75 btn btn-success text-white">
                            Complete
                        </button>
                    </form>
                </div>
                <div th:unless="${todo.status}" class="col-3 d-flex px-0">
                    <form th:action="@{/toggle-status/{id}(id=${todo.id})}" method="post" class="w-100 container d-flex my-0 mx-auto p-0 mr-2">
                        <input type="hidden" name="_method" value="patch">
                        <button type="submit" class="h-100 w-75 btn btn-danger text-white">
                            Incomplete
                        </button>
                    </form>
                </div>
<!-- ~ ~ ~ ~ ~ ~ Contents to add this time ~ ~ ~ ~ ~ -->

            </div>
        </div>
    </div><script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAony" cross </script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anony
</body>
</html>

th:if=”${todo.status}”

It means that if status is true, that is, it has been completed. It is displayed in green as completion.

Also, although method=”post” is set, @PatchMapping was used in the previous controller edit.

Actually, HTML doesn’t support the function to request Patch. So if you want to make a request with Patch, you need to devise it.

In Thymeleaf

<input type="hidden" name="_method" value="patch"> And if you want to

in application.property

spring.mvc.hiddenmethod.filter.enabled=true

When you add, you can use type=”hidden” and send a Patch request.

Controller, Service, TodoEntity summary

Contoller


package com.example.todo;

import com.example.todo.dao.TodoEntity;
import lombok.RequiredArgsConstructor;
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.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;

import java.util.List;

@Controller
@RequiredArgsConstructor
public class TodoController {

    private final TodoService todoService;
    @GetMapping("/top")
    public String top(Model model){
        List<TodoEntity> todoEntityList = todoService.findAllTodo();
        model.addAttribute("todoList", todoEntityList);
        return "top";
    }

    @PostMapping("/register")
    public String register(@ModelAttribute TodoForm formData) {
        todoService.setTodo(formData);
        return "redirect:/top";
    }

    @PatchMapping("/toggle-status/{id}")
    public String switchStatus(@PathVariable("id") Long todoId) {
        todoService.switchTodo(todoId);
        return "redirect:/top";
    }
}

Service


package com.example.todo;

import com.example.todo.dao.TodoEntity;
import com.example.todo.dao.TodoRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Optional;

@Service
@RequiredArgsConstructor
public class TodoService {
    private final TodoRepository todoRepository;

    public List<TodoEntity> findAllTodo() {
        return todoRepository.findAll();
    }

    public void setTodo(TodoForm formData) {
        TodoEntity todo = new TodoEntity();
        todo.setTitle(formData.getTitle());
        todo.setDeadline(formData.getDeadline());
        todoRepository.save(todo);
    }

    public TodoEntity findTodoById(Long todoId) {
        Optional<TodoEntity> todoResult = todoRepository.findById(todoId);
        // handle exceptions
        return todoResult.get();
    }

    public void switchTodo(Long todoId) {
        TodoEntity todoEntity = findTodoById(todoId);
        todoEntity.setStatus(!todoEntity.isStatus());
        todoRepository.save(todoEntity);
    }
}

Entity


package com.example.todo.dao;

import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;

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 java.time.LocalDate;
import java.time.LocalDateTime;

@Entity
@Getter
@Setter
@Table(name="todo")
public class TodoEntity {
    @Id
    @Column
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name="title")
    private String title;

    @Column(name="deadline")
    private LocalDate deadline;

    @Column(name="status")
    private boolean status;

    @CreationTimestamp
    @Column(name="create_time")
    private LocalDateTime createTime;

    @UpdateTimestamp
    @Column(name="update_time")
    private LocalDateTime updateTime;
}

The current TODO app is as shown above.