4-2. 목록 페이지와 뒤로 가기 문제
목록 페이지에서 각 게시물 제목에 <a> 태그를 적용해서 조회 페이지로 이동하게 처리한다. 최근에 웹페이지들은 사용자들의 트래픽을 고려해 목록 페이지에서 새창을 띄워서 조회 페이지로 이동하는 방식을 선호하지만 전통적인 방식에서는 현재창 내에서 이동하는 방식을 사용한다.
조금 관심을 가지고 웹페이지들을 이용하다 보면 의외로 이러한 처리가 제대로 되지 않는 경우를 많이 보게 된다. 예를 들어, '뒤로 가기'를 하면 다시 다운로드를 시도하거나 경고창이 뜨는 경험들을 할 수 있다. '뒤로 가기'의 해결은 조금 어려운 내용들이 나올 수 있으므로 학습에 어려움이 있다면 건너뛰는 선택도 나쁘지 않다. (난 못먹어도 고다)
- 목록에서 조회 페이지로의 이동
list.jsp 페이지는 아래와 같이 조금 수정한다.
(아마 fmt 부분에서 unknown tag라는 경고문구가 뜰텐데 그럴땐
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> 이코드를 삽입해 주면 된다.)
<tr>
<td><c:out value="${board.bno }"/></td>
<td><a href='/board/get?bno=<c:out value="${board.bno }"/>'>
<c:out value="${board.writer }"/></a></td>
<td><fmt:formatDate pattern="yyyy-MM-dd"
value="${board.regdate }"/></td>
<td><fmt:formatDate pattern="yyyy-MM-dd"
value="${board.updateDate }"/></td>
</tr>
브라우저를 통해 화면을 확인해 보면 각 게시물의 제목에 링크가 걸리는 것을 확인할 수 있고, 제목을 클릭하면 정상적으로 조회 페이지로 이동하는 것을 볼 수 있다. 조회 페이지로의 이동은 JavaScript를 이용해서 처리할 수도 있고, 위와 같이 직접 <a> 태그를 이용해서도 처리가 가능하다. 만일 조회 페이지를 이동하는 방식이 아니라 '새창'을 통해서 보고 싶다면, <a> 태그의 속성으로 target='_black'를 지정하면 된다. <a> 태그와 <form> 태그에는 target 속성을 지정할 수 있는데 '_blank'는 새로운 창에서 처리된다.
예제는 target 속성을 지정하지 않고 사용하는 전통적인 방식을 이용하도록 한다.
- 뒤로 가기의 문제
동일한 페이지 내에서 목록 페이지와 조회 페이지의 이동은 정상적으로 처리된 것 같아 보이지만 한 가지 문제가 남아있다. '등록 -> 목록 -> 조회' 까지는 순조롭지만 브라우저의 '뒤로 가기'를 선택하는 순간 다시 게시물의 등록 결과를 확인하는 방식으로 동작한다는 것이다.
이러한 문제가 생기는 원인은 브라우저에서 '뒤로 가기'나 '앞으로 가기'를 하면 서버를 다시 호출하는 게 아니라 과거에 자신이 가진 모든 데이터를 활용하기 때문이다. 브라우저에서 조회 페이지와 목록 페이지를 여러 번 앞으로 혹은 뒤로 이동해도 서버에서는 처음에 호출을 제외하고 별다른 변화가 없는 것을 확인할 수 있다.
이 문제를 해결하려면 window의 history 객체를 이용해서 현재 페이지는 모달창을 띄울 필요가 없다고 표시를 해 두는 방식을 이용해야 한다. window 의 history 객체는 스택 구조로 동작하기 때문에 아래 내용으로 원리를 살펴 보겠다.
1) 사용자가 브라우저를 열고 최초로 '/board/list' 를 호출한다. 이를 history에 쌓으면서 현재 페이지는 모달창을 보여줄 필요가 없다는 표시를 해둔다.
2) 사용자가 '/board/register'를 호출하면 스택의 상단에 '/board/register'가 쌓이게 된다. 만일 이 상태에서 '뒤로 가기'를 실행하면 아래쪽의 '/board/list' (1) 이 보여지게 되는데 이때 심어둔 표시를 이용해서 모달창을 띄울 필요가 없다는 것을 확인 할 수 있다.
3) 사용자가 등록을 완료하고 '/board/list'가 호출되게 되면 브라우저에서 '앞으로 가기'나 '뒤로 가기'로 이동한 것이 아니므로 스택에 상단에 추가된다. 등록 직후에 '/board/list'로 이동한 경우에는 모달창이 동작한다.
4) 3)번 과정에서 모달창을 보여준 후에는 스택의 상단에 모달창이 필요하지 않다는 표시를 해주어야한다. 이후에 '/board/register'를 호출하면 (스택의 아래부터 /board/list (모달x) -> /board/register -> /board/list (모달x) -> /board/register) 이런식으로 쌓여 있게 된다.
window.history 객체를 조작하는 것은 이론적으로는 복잡해 보이지만, 사실 코드는 아래와 같이 약간의 변화만 있다.
기존과 달라진 점은 맨 마지막에 추가된 history.replaceState() 부분과 checkModal()에서 history.state를 체크하는 부분이다. JavaScript의 처리는 우선 checkModal()을 실행하는데, 만일 등록된 후에 이동한 것이라면 위의 3번 과정처럼 되기때문에 모달창이 보이게 된다.
모달창이 보이는 여부와 관계없이 JavaScript의 모든 처리가 끝나게 되면 history에 쌓이는 상태는 모달창을 보여줄 필요가 없는 상태가 된다.
5. 게시물의 수정/삭제 처리
게시물의 수정 작업은 일반적으로 1) 조회 페이지에서 직접 처리하는 방식이나 2) 별도의 수정/삭제 페이지를 만들어서 해당 페이지에서 수정과 삭제를 처리하는 방식을 많이 사용한다. 최근에는 게시물의 조회 페이지에서 댓글 등의 대한 처리가 많아지면서 수정과 삭제는 별개의페이지에서 하는 것이 일반적이다.
조회 페이지에서는 GET 방식으로 처리되는 URL을 통해서 수정/삭제 버튼이 존재하는 화면을 볼 수 있게 제작해야 한다. 수정 혹은 삭제 작업은 POST 방식으로 처리되고, 결과는 다시 목록 화면에서 확인할 수 있는 형태로 제작한다.
5-1. 수정/삭제 페이지로 이동
BoardController에서 수정/삭제가 가능한 화면으로 이동하는 것은 조회 페이지와 같다. 따라서 기존의 get()메서드를 조금 수정해서 화면을 구성한다.
@GetMapping({"/get", "/modify"})
public void get(@RequestParam("bno") Long bno, Model model) {
log.info("/get or modify");
model.addAttribute("board", service.get(bno));
}
@GetMapping 이나 @PostMapping 등에는 URL을 배열로 처리할 수 있으므로, 위와 같이 하나의 메서드로 여러 URL을 처리할 수 있다.
브라우저에서는 '/board/modify?bno=13'과 같은 방식으로 처리하므로, views폴더 내 modify.jsp를 작성한다.
modify.jsp는 get.jsp와 같지만 수정이 가능한 '제목'이나 '내용'등이 readonly 속성이 없도록 작성한다. POST 방식으로 처리하는 부분을 위해서는 <form> 태그로 내용들을 감싸게 한다.
<form role="form" action="/board/modify" method="post">
<div class="form-group">
<label>Bno</label> <input class="form-control" name='bno'
value = '<c:out value="${board.bno }"/>' readonly = "readonly">
</div>
<div class="form-group">
<label>Title</label> <input class="form-control" name='title'
value = '<c:out value="${board.title }"/>'>
</div>
<div class="form-group">
<label>Text area</label>
<textarea class="form-control" rows="3" name='content' readonly="readonly">
<c:out value="${board.content }" />
</textarea>
</div>
<div class="form-group">
<label>Writer</label> <input class="form-control" name='writer'
value = '<c:out value="${board.writer }"/>' readonly = "readonly">
</div>
<div class="form-group">
<label>RegDate</label> <input class="form-control" name='regDate'
value = '<fmt:formatDate pattern = "yyyy/MM/dd"
value="${board.regdate}"/>' readonly = "readonly">
</div>
<div class="form-group">
<label>UpdateDate</label> <input class="form-control" name='updateDate'
value = '<fmt:formatDate pattern = "yyyy/MM/dd"
value="${board.updateDate}"/>' readonly = "readonly">
</div>
<button type="submit" date-oper='modify' class ="btn btn-default">Modify</button>
<button type="submit" date-oper='remove' class ="btn btn-default">Remove</button>
<button type="submit" date-oper='list' class ="btn btn-default">List</button>
</form>
<form> 태그는 action 속성을 '/board/modify'로 지정했지만, 삭제를 하면 '/board/remove'와 같이 action 속성의 내용을 수정해서 사용하게 된다. 게시물의 '제목', '내용'은 수정이 가능한 형태로 사용해서 사용자가 편집할 수 있게 한다. 등록일과 수정일은 나중에 BoardVO로 수집되어야 하므로 날짜 포맷을 'yyyy/mm/dd'의 포맷으로 해야한다 (만일 포맷이 맞지 않으면 파라미터 수집 부분에 문제가 생기므로 주의가 필요하다.) 마지막에는 '수정/삭제/목록' 등의 버튼을 추가했다.
브라우저에서는 'http://localhost:8080/board/modify?bno=19'와 같이 게시물 번호를 이용해서 수정페이지가 정상적으로 출력되는지 확인한다
JavaScript에서는 위의 버튼에 따라서 다른 동작을 할 수 있도록 수정해야 한다.
<script type="text/javascript">
$(document).ready(function(){
var formObj = $("form");
$('button').on("click", function(e){
e.preventDefault();
var operation = $(this).data("oper");
console.log(operation);
if(operation === 'remove'){
formObj.attr("action", "/board/remove");
}else if(operation === 'list'){
//move to list
self.location="/board/list";
return;
}
formObj.submit();
});
});
JavaScript에서는 <button> 태그의 'data-oper'속성을 이용해서 원하는 기능 동작을 하도록 처리한다. <form> 태그의 모든 버튼은 기본적으로 submit으로 처리하기 때문에 e.preventDefault()로 기본 동작을 막고 마지막에 직접 submit()을 수행한다.
5-2. 게시물 수정/삭제 확인
화면에서 게시물을 수정한 후에 'modify'버튼을 통해서 BoardController에 수정을 요청한다.
화면에서 'Remove'버튼을 클릭하게 되면 <form> 태그의 action 값이 '/board/remove'가 되고 데이터들이 전송된다. 물론 BoardController에서는 bno 값 하나만 필요하지만 처리에는 문제가 없다. 삭제 시 BoardController에는 아래와 같은 로그가 기록되게 된다.
5-3. 조회 페이지에서 <form> 처리
게시물의 조회 페이지에서는 수정과 삭제가 필요한 페이지로 링크를 처리해야 한다. 직접 버튼에 링크를 처리하는 방식을 사용하여 처리하였지만, 나중에 다양한 상황을 처리하기 위해서 <form> 태그를 이용해서 수정한다.
사용자가 버튼을 클릭하면 operForm이라는 id를 가진 <form> 태그를 전송해야 하므로 추가적인 JavaScript 처리가 필요하다.
<script type="text/javascript">
$(document).ready(function(){
var operForm = $("#operForm");
$("button[data-oper='modify']").on("click", function(e){
operForm.attr("action", "/board/modify").submit();
});
$("button[data-oper='list']").on("click", function(e){
operForm.find("#bno").remove();
operForm.attr("action", "/board/list");
operForm.submit();
});
});
</script>
사용자가 수정 버튼을 누르는 경우에는 bno 값을 같이 전달하고 <form> 태그를 submit 시켜서 처리한다. 만일 사용자가 list로 이동하는 경우에는 아직 아무런 데이터도 필요하지 않으므로 <form> 태그 내의 bno 태그를 지우고 submit을 통해서 리스트 페이지로 이동한다.
5-4. 수정 페이지에서 링크 처리
수정 페이지에서는 사용자가 다시 목록 페이지로 이동할 수 있도록 하기 위해 JavaScript 내용을 조금 수정한다.
<script type="text/javascript">
$(document).ready(function(){
var formObj = $("form");
$('button').on("click", function(e){
e.prevenDefault();
var operation = $(this).data("oper");
console.log("operation : " + operation);
debugger;
if(operation === 'remove'){
formObj.attr("action", "/board/remove");
}else if(operation === 'list'){
//move to list
formObj.attr("action", "/board/list").attr("method", "get");
formObj.empty();
}
formObj.submit();
});
});
</script>
수정된 내용은 클릭한 버튼이 List인 경우 action 속성과 method 속성을 변경한다.
'/board/list'로의 이동은 아무런 파라미터가 없기 때문에 <form> 태그의 모든 내용은 삭제한 상태에서 submit()을 진행한다. 이후에 코드는 실행되지 않도록 return을 통해서 제어한다. (? 리턴이 어딨지?)
* 진짜 한 몇시간동안 삽질했는데
전에 반응형 웹처리를 위해서 footer.jsp에서 작성했었던 DataTable 코드를 삭제해야 오류가 줄어든다.... 라이브러리가 없나보다. 부트스트랩이 생소해서 코드 의존성에 대해서 잘 파악하지 못해 생긴 일이다... 어떻게 돌아가는지 아직 잘 모르겠다 ㅠㅠ
'Spring' 카테고리의 다른 글
[20] 기본적인 웹 게시물 관리 - 페이징 화면 처리 (0) | 2019.12.25 |
---|---|
[19] 기본적인 웹 게시물 관리 - MyBatis와 스프링에서 페이징 처리 (0) | 2019.12.23 |
[18] 기본적인 웹 게시물 관리 - 오라클 데이터베이스 페이징 처리 2 (0) | 2019.12.23 |
[17] 기본적인 웹 게시물 관리 - 오라클 데이터베이스 페이징 처리 (0) | 2019.12.23 |
jquery.min.js:2 Uncaught TypeError: $(...).DataTable is not a function (0) | 2019.12.20 |