본 예제에는 두 가지의 댓글 작성법이 있는 데,
JS(Ajax)에 대한 보다 깊은 이해를 위해 다른 방식으로 구현했다.
즉, 기존(board6)에는 게시물에 작성하는 댓글과 댓글에 대한 댓글을 구분하여 처리하였다.
두 방식 모두 처리하는 코드는 유사해서 하나로 할 수도 있지만
이해를 위해 다르게 구현했다.
차이는 저장한 데이터를 서버에 저장한 후 사용자에게 보여주는 방식에 있다.
먼저 게시물에 대한 댓글은 작성한 댓글을 서버에 저장하고,
반환 받은 값(JSON: 댓글번호 + 서버로 전송한 값)을 적절하게 구성해서
화면 출력에 필요한 모든 구성(HTML 태그)들을 모두 JS (JQuery)로 동적으로 생성한다.
이 방법은 꼭 필요한 데이터만 주고 받지만 제법 수준 있는 JS 실력을 필요로 하고
작성된 코드도 복잡하다.
또 다른 방법은 서버에서 저장 후 반환될 때 HTML로 잘 정리된 값을 만들고
클라이언트에서는 그냥 화면에 출력만 해주면 되는 방식이다.
기존 코드
function fn_formSubmit(){
if ( $.trim($("#rewriter1").val()) == "") {
alert("작성자를 입력해주세요.");
$("#rewriter1").focus();
return;
}
if ($.trim($("#rememo1").val()) == "") {
alert("글 내용을 입력해주세요.");
$("#rememo1").focus();
return;
}
$("#form1").submit();
}
Ajax
function fn_formSubmit(){
if ( $.trim($("#rewriter1").val()) == "") {
alert("작성자를 입력해주세요.");
$("#rewriter1").focus();
return;
}
if ($.trim($("#rememo1").val()) == "") {
alert("글 내용을 입력해주세요.");
$("#rememo1").focus();
return;
}
$.ajax({
url: "board7ReplySaveAjax",
type:"post",
data: {"brdno": $("#brdno1").val(), "rewriter": $("#rewriter1").val(), "rememo": $("#rememo1").val()},
success: function(result){
if (result!=="") {
var div = $("<div>");
div.attr("id", "replyItem" + result);
div.appendTo($("#replyList"));
div.css({border: "1px solid gray", width: "600px", "padding": "5px", "margin-top": "5px", "margin-left": "0px", display:"inline-block"});
div.text($("#rewriter1").val() + " 방금" );
$("<a>",{
text: "삭제",
href: "#",
click: function (){fn_replyDelete(result)}
}).appendTo(div);
$("<a>").attr("href", "#").text("수정").click(function (){fn_replyUpdate(result)}).appendTo(div);
var href = $("<a>");
href.attr("href", "#");
href.text("댓글");
href.click(function (){fn_replyReply(result)});
href.appendTo(div);
var reply=$("<div>").appendTo(div);
reply.attr("id", "reply" + result);
reply.html($("#rememo1").val());
$("#rewriter1").val("");
$("#rememo1").val("");
alert("저장되었습니다.");
} else{
alert("서버에 오류가 있어서 저장되지 않았습니다.");
}
}
})
}
BoardReadAjax.jsp
이상의 코드에 보이는 것처럼
기존에는 폼 태그에 대하여 submit만 진행하면 됐다.
Ajax로 변환한 코드는 코드 양에서부터 부담스럽고 복잡해 보인다.
즉, submit을 할 때는 웹 브라우저가 알아서 데이터를 정리해서 보내는데
Ajax는 삭제에서 다루었지만 전송할 값들을 개발자가 모두 지정해야 하는 불편함이 있다.
(이 이유를 포함해서 이런 저런 이유로
Ajax 사용은 꼭 필요한 곳에만 적용해야 유지보수를 쉽게 할 수 있다.
어떤 웹 사이트는 Ajax로 도배가 되어 있는데 사용하기도, 개발/유지 보수하기도 힘들뿐이라고 생각한다.)
댓글 작성시 필요한 게시글 번호(brdno), 작성자(rewriter1), 댓글내용(rememo1)을 data로 구성해 준다.
댓글 번호(reno)는 서버에 저장 후 반환 되는 값이니 전달하지 않는다.
서버 주소(url)은 board7ReplySaveAjax로
삭제와 같이 기존 컨트롤 이름(board7ReplySave)과 유사하게 작성하였다
@RequestMapping(value = "/board7ReplySave")
public String board7ReplySave(HttpServletRequest request, BoardReplyVO boardReplyInfo) {
boardSvc.insertBoardReply(boardReplyInfo);
return "redirect:/board7Read?brdno=" + boardReplyInfo.getBrdno();
}
@RequestMapping(value = "/board7ReplySaveAjax")
public void board7ReplySaveAjax(HttpServletResponse response, BoardReplyVO boardReplyInfo) {
ObjectMapper mapper = new ObjectMapper();
response.setContentType("application/json;charset=UTF-8");
boardSvc.insertBoardReply(boardReplyInfo);
try {
response.getWriter().print( mapper.writeValueAsString(boardReplyInfo.getReno()));
} catch (IOException ex) {
System.out.println("오류: 댓글 저장에 문제가 발생했습니다.");
}
}
Board7Ctr.java
이상의 코드에서 확인되지만 삭제와 유사하다.
차이점은 반환 값이 저장하면서 부여된 댓글 번호(reno)라는 것이다.
클라이언트에서 전송 받은 클래스(boardReplyInfo)를 같이 넘겨도 되지만
클라이언트에서 전송한 값은 클라이언트에 있고 최소값만 네트워크로 이동하고자
클라이언트에 없는 댓글 번호만 반환했다.
success에서 반환 값을 result로 받아서
값이 없으면 오류 메시지를,
값이 있으면 사용자에게 보여줄 화면 구성을 하게 된다.
여기서는 사용자에게 보여줄 화면 구성 코드에 대한 상세한 설명은 하지 않을 것이다.
이러한 개발 방법이 있다는 것만 기억하고
가급적 다음 방법으로 개발하는 것이 좋기 때문이다.
success에 작성된 코드는
다음 코드와 같이 댓글 리스트를 생성하는 코드에서
리스트 중 하나의 댓글에 대한 코드를(forEach를 제외한 코드) 생성 하는 JS 코드이다.
<div id="replyList">
<c:forEach var="replylist" items="${replylist}" varStatus="status">
<div id="replyItem<c:out value="${replylist.reno}"/>"
style="border: 1px solid gray; width: 600px; padding: 5px; margin-top: 5px; margin-left: <c:out value="${20*replylist.redepth}"/>px; display: inline-block">
<c:out value="${replylist.rewriter}"/> <c:out value="${replylist.redate}"/>
<a href="#" onclick="fn_replyDelete('<c:out value="${replylist.reno}"/>')">삭제</a>
<a href="#" onclick="fn_replyUpdate('<c:out value="${replylist.reno}"/>')">수정</a>
<a href="#" onclick="fn_replyReply('<c:out value="${replylist.reno}"/>')">댓글</a>
<br/>
<div id="reply<c:out value="${replylist.reno}"/>"><c:out value="${replylist.rememo}"/></div>
</div><br/>
</c:forEach>
</div>
BoardReadAjax.jsp
즉 HTML을 JS를 이용하여 동적으로 생성하는 것이라 이상과 같이 복잡한 것이다.
개인적이 발전을 위해서는 이상의 두 코드(JS/HTML)을 비교해서 익혀두면 좋을 것이다.
이러한 복잡한 코드를 서버에서 HTML로 처리해서 반환 받으면 쉽게 처리 할 수 있다.
먼저, 다음과 같이 board7ReplySaveAjax4Reply 컨트롤을 만든다.
@RequestMapping(value = "/board7ReplySaveAjax4Reply")
public String board7ReplySaveAjax4Reply(BoardReplyVO boardReplyInfo, ModelMap modelMap) {
boardSvc.insertBoardReply(boardReplyInfo);
modelMap.addAttribute("replyInfo", boardReplyInfo);
return "board7/BoardReadAjax4Reply";
}
기존 코드와 다른 점은 modelMap으로 반환 값을 주고 JSP 파일을 지정한다는 것이다.
기존에는 저장 후
return "redirect:/board7Read?brdno=" + boardReplyInfo.getBrdno();
- Ajax로 키 값만 전송해서 클라이언트에서 해당 글만 생성
response.getWriter().print( mapper.writeValueAsString(boardReplyInfo.getReno()));
했는데,
이번 예제는 Ajax를 기존 MVC 예제처럼 댓글에 대한 정보를
JSP(BoardReadAjax4Reply.jsp)로 넘겨서 처리한다.
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<div id="replyItem<c:out value="${replyInfo.reno}"/>"
style="border: 1px solid gray; width: 600px; padding: 5px; margin-top: 5px; margin-left: <c:out value="${20*replyInfo.redepth}"/>px; display: inline-block">
<c:out value="${replyInfo.rewriter}"/> <c:out value="${replyInfo.redate}"/>
<a href="#" onclick="fn_replyDelete('<c:out value="${replyInfo.reno}"/>')">삭제</a>
<a href="#" onclick="fn_replyUpdate('<c:out value="${replyInfo.reno}"/>')">수정</a>
<a href="#" onclick="fn_replyReply('<c:out value="${replyInfo.reno}"/>')">댓글</a>
<br/>
<div id="reply<c:out value="${replyInfo.reno}"/>"><c:out value="${replyInfo.rememo}"/></div>
</div><br/>
BoardReadAjax4Reply.jsp
BoardReadAjax4Reply.jsp 파일의 내용은 앞서 댓글 리스트에서 사용된 HTML 코드를 그대로 사용한 것이다.
댓글 리스트에서는 forEach문을 사용하여 List를 처리했고
여기서는 하나의 댓글이므로 replyInfo(BoardReplyVO)로 하나의 클래스가 사용되었다.
function fn_replyReplySave(){
if ( $.trim($("#rewriter3").val()) == "") {
alert("작성자를 입력해주세요.");
$("#rewriter3").focus();
return;
}
if ($.trim($("#rememo3").val()) == "") {
alert("글 내용을 입력해주세요.");
$("#rememo3").focus();
return;
}
var formData = $("#form3").serialize();
$.ajax({
url: "board7ReplySaveAjax4Reply",
type:"post",
data : formData,
success: function(result){
if (result!=="") {
var parent = $("#reparent3").val();
$("#replyItem"+parent).after(result);
$("#replyDialog").hide();
alert("저장되었습니다.");
} else{
alert("서버에 오류가 있어서 저장되지 않았습니다.");
}
}
})
}
서버에서 작성된 HTML이 result 변수에 담겨서 success의 콜백 함수를 호출한다.
클라이언트에서는 부모 댓글을 찾아서($("#replyItem"+parent)) 부모댓글 다음(after)에
result 값을 넣어주면 자동으로(? – JS/JQuery의 장점) 신규 댓글이 생성되어 보여지게 된다.
부모 댓글을 찾기 위해 댓글 항목을 출력할 때,
하나의 댓글을 나타내는 DIV 에 "replyitem"와 댓글 번호를 이용한 고유 이름(ID)을 부여 했다.
서버로 데이터를 전송하기 위해 JSON 데이터를 생성할 때도 하나씩 키와 값을 지정하지 않고
JQuery에서 제공하는 serialize를 이용했다.
serialize는 Form ID만 지정해주면 자동으로 JSON data가 생성된다.
정리하면
JQuery Ajax를 사용할 때
댓글 삭제나 수정처럼 JSON으로 데이터를 전송하고 받아서 처리하기도 하고
댓글 저장(들)처럼 HTML로 받아서 처리할 때가 있다.
잘 선택해서 사용하면 빠르고 안정적인 사이트를 구축할 수 있을 것이다.