현재까지 만든 이 /list-todos 페이지에 버튼을 추가로 넣어서, 해당 버튼을 누르면 새로운 Todo 페이지("/add-todo")로 리다이렉션하게끔 만들 것이다.
결론부터 말하자면, /add-todo URL에서 description 작성 후 submit을 눌렀더니, /add-todo URL이 아닌 /list-todos 페이지로 리다이렉션된 것을 볼 수 있다. 하지만 여전히 추가된 todo는 적용되지 않았다.
Bootstrap에서 버튼을 만들 때는 btn 클래스를 사용하며, 버튼을 클릭 가능하게 하려면 btn-success 클래스도 함께 추가한다.
<a href = "add-todo" class = "btn btn-success">Add Todo</a>
해당 코드를 listTodos.jsp에 추가한 실행결과는 다음과 같다.
이제, /add-todo에 대응되는 컨트롤러 코드를 TodoController클래스에 추가하고, addTodo.jsp 파일을 만들겠다.
// add-todo -> showNewTodoPage메서드만 가져왔다.
@RequestMapping("/add-todo")
public String ShowNewTodoPage() {
return "addTodo";
}
// addTodo.jsp
<html>
<head>
<link href = "webjars/bootstrap/5.1.3/css/bootstrap.min.css" rel = "stylesheet">
<title>Add Todo Page</title>
</head>
<body>
<div class = "container">
<h1>Enter Todo Details</h1>
<script src="webjars/bootstrap/5.1.3/js/bootstrap.min.js"></script>
<script src="webjars/jquery/3.6.0/jquery.min.js"></script>
</div>
</body>
</html>
<html>
<head>
<link href = "webjars/bootstrap/5.1.3/css/bootstrap.min.css" rel = "stylesheet">
<title>Add Todo Page</title>
</head>
<body>
<div class = "container">
<h1>Enter Todo Details</h1>
<form method = "post">
Description : <input type = "text" name = "description"> <br>
<input type = "submit" class = "btn btn-success" />
</form>
<script src="webjars/bootstrap/5.1.3/js/bootstrap.min.js"></script>
<script src="webjars/jquery/3.6.0/jquery.min.js"></script>
</div>
</body>
</html>
실행결과
하지만 해당 Description에 텍스트를 입력하고 제출을 눌러도, 다시 똑같은 페이지인 /add-todo로 돌아온다. 하지만 내가 원했던 건, /add-todo에서 Description에 텍스트를 입력하고 제출을 누른 후 -> /list-todos 페이지로 가서 추가된 todo를 볼 수 있게 되는 것이었다.
왜 /list-todos가 아닌 /add-todo로 돌아올까?
왜냐하면, ShowNewTodoPage()에서 GET, POST 등 모든 종류의 request를 하나의 메서드에서 처리하고 있기 때문이었다.
// add-todo -> Get, POST
@RequestMapping("/add-todo")
public String ShowNewTodoPage() {
return "addTodo";
}
LoginController에서의 작업과 마찬가지로 submit 버튼 누르기 전후로 GET 메서드 -> POST 메서드로 바뀐다는 사실을 이용하면 된다. 다시 말해, LoginController에서와 똑같이, TodoController에서도 GET메서드와 POST메서드에 따라 다른 메서드를 만들어 별도의 처리를 해주어, 페이지 리다이렉션을 할 수 있게 해야 한다.
// TodoController의 Get, POST 메서드에 따른 다른 처리를 해주는 메서드들
// add-todo - GET
@RequestMapping(value = "/add-todo", method = RequestMethod.GET)
public String showNewTodoPage() {
return "addTodo";
}
// add-todo - POST
@RequestMapping(value = "/add-todo", method = RequestMethod.POST)
public String addNewTodoPage() {
return "listTodos";
}
위 코드처럼, submit 버튼을 누르기 전에는 addTodo.jsp화면을 보여주고, submit 버튼을 누르면 POST 메서드로 바뀌어서 listTodos.jsp 화면을 보여준다.
다음의 실행결과는, /add-todo에서 description을 적고 submit 버튼을 누른 뒤, listTodos.jsp 화면으로 리다이렉션이 될 것을 기대한 화면이다. 헷갈리지 말아야 할 점은 해당 웹사이트의 URL은 localhost:8080/add-todo인 것이다. (listTodos.jsp는 보여지는 뷰 화면이고 URL과 다른 개념)
그런데 왜, listTodos.jsp에서는 빈 리스트를 보여주었을까?
아래의 코드를 보며 원인을 생각해보자.
// TodoController의 list-todos URL 메서드 부분
// list-todos
@RequestMapping("/list-todos")
public String listAllTodos(ModelMap model) {
List<Todo> todos = todoService.findByUsername("minjiki2");
model.addAttribute("todos", todos);
return "listTodos";
}
// listTodos.jsp의 body 부분
<body>
<div class = "container">
<h1>Your Todos</h1>
<table class = "table">
<thead>
<tr>
<th>id</th>
<th>Description</th>
<th>Deadline</th>
<th>is Done?</th>
</tr>
</thead>
<tbody>
<c:forEach items = "${todos}" var = "todo">
<tr>
<td>${todo.id}</td>
<td>${todo.description}</td>
<td>${todo.deadline}</td>
<td>${todo.done}</td>
</tr>
</c:forEach>
</tbody>
</table>
<a href = "add-todo" class = "btn btn-success">Add Todo</a>
<script src="webjars/bootstrap/5.1.3/js/bootstrap.min.js"></script>
<script src="webjars/jquery/3.6.0/jquery.min.js"></script>
</div>
</body>
왜 빈 리스트를 보여주였냐면, 결론부터 말하자면 model scope와 URL이 무엇인지 파악하는 것이 핵심이었다.
list-todos request URL을 처리해주는 컨트롤러코드는, TodoService에 하드코딩으로 넣은 리스트값을 model에 추가해, 리턴하는 뷰 이름인 "listTodos.jsp"를 화면에 보여줌으로써, 빈 리스트가 아닌 3개의 값들을 보여줄 수 있었다.
하지만!
addNewTodoPage()메서드의 URL은 /add-todo URL이다!! /list-todos request URL이 아니라는 거다. 즉, 해당 컨트롤러 코드에는 listTodos.jsp로 보내는 그 어떠한 모델값도 없다. model scope는 by default로, request scope와 동일하기에 /list-todos에서 추가했던 todos 모델은 /add-todos에서는 해당 모델값을 적용할 수 없기에, "listTodos.jsp"에서 ${todos}를 불러올 수 없었던 것이다!!
그럼 이제 showNewTodoPage()가 리턴하는 addTodo.jsp에서 description을 submit해, /add-todo request URL을 받는 addNewTodoPage()에서 listTodos.jsp 뷰를 보여주되, 빈 리스트를 보여주지 않게 만들어줘야 한다.
만일 해결책으로 빈 리스트를 model로 보내주는 코드를 또 쓴다면, 코드가 중복되어서 보기에 좋지 않다.
그보다는, addNewTodoPage()메서드에서 리턴하는 값을 model을 추가하지 않은 addNewTodoPage()의 listTodos.jsp가 아니라, model을 추가한 listAllTodos()의 listTodos.jsp 페이지로 리다이렉션할 것이다.
수정한 코드 부분만 따오면 다음과 같다.
// list-todos
@RequestMapping("/list-todos")
public String listAllTodos(ModelMap model) {
List<Todo> todos = todoService.findByUsername("minjiki2");
model.addAttribute("todos", todos);
return "listTodos";
}
@RequestMapping(value = "/add-todo", method = RequestMethod.POST)
public String addNewTodoPage() {
// return "listTodos";
return "redirect:list-todos";
}
다시 말해, addNewTodoPage()메서드에서 listTodos 뷰 이름을 리턴하는 것이 아니라!
redirect:list-todos 를 통해서 /list-todos URL로 페이지 리다이렉션을 하는 것이다!! 그럼 /list-todos URL과 mapping되어 있는 listAllTodos메서드를 다시 실행하게 된다.
즉, /add-todo에서 description 작성 후 submit을 눌러 POST method가 되면, 다시 /list-todos URL로 가는 것이다.
실행결과, /add-todo URL에서 description 작성 후, submit을 눌렀더니, URL이 /list-todos로 페이지 리다이렉션이 된 것을 확인할 수 있었다. /list-todos URL과 mapping된 listAllTodos메서드를 실행해, 기존의 3개의 정적 데이터값만 화면에 보이게 된 것이다.
그렇지만 여전히, /add-todo URL에서 추가한 description이 Todo.java 객체에 들어가지 않아, 실행결과 여전히 초기값인 3개의 정적 리스트만 출력하고 있다. 다음 포스팅에서 해당 내용을 다뤄보자.
/add-todo URL에서 description을 추가하고 submit 버튼을 눌렀더니, return "redirect:list-todos;"
를 통해, /list-todos URL로 페이지 리다이렉션이 된 것을 확인할 수 있었다.
@RequestMapping(value = "/add-todo", method = RequestMethod.POST)
public String addNewTodoPage() {
// return "listTodos";
return "redirect:list-todos";
}
하지만 아직 Todo 내부 list 에, add-todo에서 작성한 description이 추가되지 않은 사실을 확인할 수 있었다.
이 시리즈는 Udemy 강의의 내용을 정리한 것입니다.
https://www.udemy.com/course/spring-boot-and-spring-framework-korean/