To-Do Application on Spring boot
I am going to create To-do list.
Spring boot dependencies.
- spring boot web
- jdbc postgreSQl driver
- spring- JPA
- Thyme-leaf
Entities
I am going to create two entities. They are task and Todo. It has one to many, It means todo has many task. Create the field and getter and setter methods.
TodoList class
It has id, name and List<Task>. Create getter and setter method.
Then we create bidirectional relationship between todo have task. so we create mappedBy the todo object on task. so task can create foreign key for todo list.
@Entity public class TodoList { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private String name; @OneToMany(mappedBy = "todoList", cascade = CascadeType.ALL, orphanRemoval = true,fetch = FetchType.EAGER) private List<Task> taskList= new ArrayList<>(); @ManyToOne @JoinColumn(name = "user_idn") private Users users; // no-arg constructor and getter and setter }
Task class
It has id and name and todo object. Then we create getter and setter method.
The foreign key create in this table, because we mapped this todo object is mapped on that class. joinColumn name is the foreign key name.
@Entity public class Task { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private String name; private boolean status; // private Locale locale; // date and time @ManyToOne @JoinColumn(name = "todo_list_id") private TodoList todoList; }
Repository Interface
Simple that create two interface one for task and todo which can extend the JPARepository.
public interface TaskRepository extends JpaRepository<Task, Long> { } public interface TaskRepository extends JpaRepository<Task, Long> { }
DB and JPA configuations
spring.datasource.url=jdbc:postgresql://localhost:5432/login spring.datasource.username=progres spring.datasource.password=1234 spring.datasource.driver-class-name=org.postgresql.Driver spring.jpa.hibernate.ddl-auto=update spring.jpa.show-sql=true spring.jpa.properties.hibernate.format_sql=true spring.jpa.database-platform= org.hibernate.dialect.PostgreSQLDialect
Controller class with html
We are going to create 7 task with frontend html with thyme leaf. Map the class with “/todos” using @RequestMapping.
Click the arrow or triange (|>) to see full details.
1. show all todo list
It method is used to default page of /todos page. It have List of Todo and newlist which can return to html page.
@GetMapping public String listTodoLists(Model model) { model.addAttribute("todoLists", todoListRepository.findAll()); model.addAttribute("newTodoList", new TodoList()); return "todo-lists"; }
2. create the todo list
Then we press create list button on html. we got newlist from getMapping that List pass through @modelAttribute. That list can save in repository.
@PostMapping public String createTodoList(@ModelAttribute TodoList todoList) { todoListRepository.save(todoList); return "redirect:/todos"; }
3. show tasks from todo list
Then we enter into the a list, it have many task. That can be shown by this method. we get the list by id and we pass that list of task and new task to html.
@GetMapping("/{listId}") public String viewTodoList(@PathVariable Long listId, Model model) { TodoList todoList = todoListRepository.findById(listId) .orElseThrow(() -> new IllegalArgumentException("Invalid list id")); model.addAttribute("todoList", todoList); model.addAttribute("newTask", new Task()); return "tasks"; }
4. create the task from todo list
Then we press create task button on html. we got new task from getMapping that task pass through @modelAttribute. That list can save in repository.
// Add task to a todo list @PostMapping("/{listId}/tasks") public String addTask(@PathVariable Long listId, @ModelAttribute Task task) { TodoList todoList = todoListRepository.findById(listId) .orElseThrow(() -> new IllegalArgumentException("Invalid list id")); task.setTodoList(todoList); taskRepository.save(task); return "redirect:/todos/" + listId; }
5. Toggle the task or not
Then we press todo checkbox it can be tick. Same find the task by taskId. That task setCompleted and save it again in task. Return redirect://todos/+listId, it redirect to getMapping or this Todo List.
// Toggle task completion status @PostMapping("/{listId}/tasks/{taskId}/toggle") public String toggleTask(@PathVariable Long listId, @PathVariable Long taskId) { Task task = taskRepository.findById(taskId) .orElseThrow(() -> new IllegalArgumentException("Invalid task id")); task.setCompleted(!task.isCompleted()); taskRepository.save(task); return "redirect:/todos/" + listId; }
6. delete the todo list
Then we press delete button from list on html. we remove from repository. Return the getMapping of current list.
@PostMapping("/{listId}/delete") public String deleteTask(@PathVariable Long listId) { taskRepository.deleteById(taskId); return "redirect:/todos/" + listId; }
7. delete the task from task list
Then we press delete button on html. We delete task from task repository. We return and redirect to getmapping of current task list.
@PostMapping("/{listId}/delete") public String deleteTodoList(@PathVariable Long listId) { todoListRepository.deleteById(listId); return "redirect:/todos"; }
Controller full code
@Controller @RequestMapping("/todos") public class TodoController { @Autowired private TodoListRepository todoListRepository; @Autowired private TaskRepository taskRepository; // Show all todo lists @GetMapping public String listTodoLists(Model model) { model.addAttribute("todoLists", todoListRepository.findAll()); model.addAttribute("newTodoList", new TodoList()); return "todo-lists"; } // Create new todo list @PostMapping public String createTodoList(@ModelAttribute TodoList todoList) { todoListRepository.save(todoList); return "redirect:/todos"; } // Show tasks from todo list @GetMapping("/{listId}") public String viewTodoList(@PathVariable Long listId, Model model) { TodoList todoList = todoListRepository.findById(listId) .orElseThrow(() -> new IllegalArgumentException("Invalid list id")); model.addAttribute("todoList", todoList); model.addAttribute("newTask", new Task()); return "tasks"; } // Add task to a todo list @PostMapping("/{listId}/tasks") public String addTask(@PathVariable Long listId, @ModelAttribute Task task) { TodoList todoList = todoListRepository.findById(listId) .orElseThrow(() -> new IllegalArgumentException("Invalid list id")); task.setTodoList(todoList); taskRepository.save(task); return "redirect:/todos/" + listId; } // Toggle task completion status @PostMapping("/{listId}/tasks/{taskId}/toggle") public String toggleTask(@PathVariable Long listId, @PathVariable Long taskId) { Task task = taskRepository.findById(taskId) .orElseThrow(() -> new IllegalArgumentException("Invalid task id")); task.setCompleted(!task.isCompleted()); taskRepository.save(task); return "redirect:/todos/" + listId; } // Delete a task @PostMapping("/{listId}/tasks/{taskId}/delete") public String deleteTask(@PathVariable Long listId, @PathVariable Long taskId) { taskRepository.deleteById(taskId); return "redirect:/todos/" + listId; } // Delete a todo list @PostMapping("/{listId}/delete") public String deleteTodoList(@PathVariable Long listId) { todoListRepository.deleteById(listId); return "redirect:/todos"; } }
Html code
Html has two page one for todo list and another for task list.
Todo list.html
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <title>Todo Lists</title> <link rel="icon" type="image/x-icon" href="/aaeranLogo.ico" /> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> </head> <body> <div class="container mt-4"> <h1>My Todo Lists</h1> <!-- Form to create new todo list --> <form th:action="@{/todos}" th:object="${newTodoList}" method="post" class="mb-4"> <div class="input-group"> <input type="text" th:field="*{name}" class="form-control" placeholder="New list name" required> <button type="submit" class="btn btn-primary">Create List</button> </div> </form> <!-- display the todo list --> <div th:each="todoList : ${todoLists}" class="card mb-3"> <div class="card-body"> <h2 class="card-title"> <a th:href="@{/todos/{id}(id=${todoList.id})}" th:text="${todoList.name}">List Name</a> <span class="badge bg-secondary" th:text="${todoList.taskList.size()}">0</span> </h2> <form th:action="@{/todos/{id}/delete(id=${todoList.id})}" method="post" class="d-inline"> <button type="submit" class="btn btn-sm btn-danger">Delete List</button> </form> </div> </div> </div> </body> </html>
Task.html
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <title>Tasks</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> </head> <body> <div class="container mt-4"> <h1>Tasks for <span th:text="${todoList.name}">List Name</span></h1> <a href="/todos" class="btn btn-secondary mb-3">Back to Lists</a> <!-- Form to add new task --> <form th:action="@{/todos/{id}/tasks(id=${todoList.id})}" th:object="${newTask}" method="post" class="mb-4"> <div class="input-group"> <input type="text" th:field="*{name}" class="form-control" placeholder="New task description" required> <button type="submit" class="btn btn-primary">Add Task</button> </div> </form> <div th:each="task : ${todoList.taskList}" class="card mb-2"> <div class="card-body d-flex align-items-center"> <form th:action="@{/todos/{listId}/tasks/{taskId}/toggle(listId=${todoList.id}, taskId=${task.id})}" method="post" class="me-3"> <input type="checkbox" th:checked="${task.status}" onChange="this.form.submit()" class="form-check-input" style="transform: scale(1.5);"> </form> <span th:class="${task.status} ? 'text-decoration-line-through text-muted' : ''" th:text="${task.name}" class="flex-grow-1">Task description</span> <form th:action="@{/todos/{listId}/tasks/{taskId}/delete(listId=${todoList.id}, taskId=${task.id})}" method="post" class="ms-2"> <button type="submit" class="btn btn-sm btn-danger">Delete</button> </form> </div> </div> </div> </body> </html>
Reference :
- https://www.javaguides.net/2018/09/mini-todo-management-project-using-spring-boot-springmvc-springsecurity-jsp-hibernate-mysql.html
- https://www.javaguides.net/2018/09/mini-todo-management-project-using-spring-boot-springmvc-springsecurity-jsp-hibernate-mysql.html