❌

Normal view

There are new articles available, click to refresh the page.
Before yesterdayMain stream

To-Do Application on Spring boot

I am going to create To-do list.

Spring boot dependencies.

  1. spring boot web
  2. jdbc postgreSQl driver
  3. spring- JPA
  4. 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 :

❌
❌