detail.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<jsp:include page="../layout/header.jsp"></jsp:include>
<jsp:include page="../layout/nav.jsp"></jsp:include>
<div class="container-md">
<!-- ... (기존 코드) -->
<!-- 댓글 등록 라인 -->
<div class="input-group mb-3">
<span class="input-group-text" id="cmtWriter">Writer</span>
<input type="text" class="form-control" id="cmtText" aria-label="Amount (to the nearest dollar)">
<button type="button" class="btn btn-success" id="cmtPostBtn">등록</button>
</div>
<!-- 댓글 표시 라인 -->
<ul class="list-group list-group-flush" id="cmtListArea">
<li class="list-group-item">
<div class="mb-3">
<div class="fw-bold">Writer <span class="badge rounded-pill text-bg-warning">modAt</span></div>
content
</div>
</li>
</ul>
</div>
<script type="text/javascript">
let bnoVal = `<c:out value="${bvo.bno}"/>`;
console.log(bnoVal);
</script>
<script src="/resources/js/boardComment.js"></script>
<jsp:include page="../layout/footer.jsp"></jsp:include>
게시물 상세페이지에 댓글을 입력할 곳과 댓글이 뿌려질 곳의 위치를 미리 잡아 주었다.
<div class="input-group mb-3">
<span class="input-group-text" id="cmtWriter">Writer</span>
<input type="text" class="form-control" id="cmtText" aria-label="Amount (to the nearest dollar)">
<button type="button" class="btn btn-success" id="cmtPostBtn">등록</button>
</div>
<ul class="list-group list-group-flush" id="cmtListArea">
<li class="list-group-item">
<div class="mb-3">
<div class="fw-bold">Writer <span class="badge rounded-pill text-bg-warning">modAt</span></div>
content
</div>
</li>
</ul>
그 후, detail에서 bno를 받아 js에서는 bnoVal로 사용하기 위해 script를 작성했다.
<script type="text/javascript">
let bnoVal = `<c:out value="${bvo.bno}"/>`;
console.log(bnoVal);
</script>
src > main > webapp > resources > js 하위에 boardComment 이름의 javaScript 파일을 생성하고
script를 detail.jsp에 추가했다.
<script src="/resources/js/boardComment.js"></script>
boardComment.js
console.log('boardComment.js in~!!');
console.log(bnoVal);
document.getElementById('cmtPostBtn').addEventListener('click',()=>{
const cmtText = document.getElementById('cmtText');
if(cmtText.value == null || cmtText.value == '') {
alert('댓글을 입력해주세요');
cmtText.focus();
return false;
} else {
let cmtData = {
bno : bnoVal,
writer : document.getElementById('cmtWriter').innerHTML,
content : cmtText.value
};
console.log(cmtData);
// 댓글 등록
postCommentToServer(cmtData).then(result =>{
if(result == '1') {
console.log('댓글 등록 성공~!!');
cmtText.value = "";
}
// 화면에 뿌려
spreadCommentList(cmtData.bno);
})
}
});
async function postCommentToServer(cmtData) {
try {
const url = "/comment/post";
const config = {
method : "post",
headers : {
'content-type' : 'application/json; charset=utf-8'
},
body : JSON.stringify(cmtData)
};
const resp = await fetch(url, config);
const result = await resp.text();
return result;
} catch (error) {
console.log(error);
}
};
async function getCommentListFromServer(bno) {
try {
const resp = await fetch("/comment/" + bno);
const result = await resp.json();
return result;
} catch (error) {
console.info(error);
}
};
function spreadCommentList(bno) {
getCommentListFromServer(bno).then(result=>{
console.log(result);
const ul = document.getElementById('cmtListArea');
ul.innerHTML = '';
if(result != null) {
for(let cvo of result) {
let li = `<li class="list-group-item" data-cno="${cvo.cno}" data-writer="${cvo.writer}">`;
li += `<div class="mb-3">`;
li += `<div class="fw-bold">${cvo.writer} `;
li += `<span class="badge rounded-pill text-bg-warning">${cvo.modAt}</span></div>`;
li += `${cvo.content}`;
li += `</div>`;
li += `<button type="button" class="btn btn-sm btn-outline-success cmtModBtn" data-bs-toggle="modal" data-bs-target="#myModal">Eidt</button>`;
li += `<button type="button" class="btn btn-sm btn-outline-danger cmtDelBtn">Delete</button>`;
li += `</li>`;
ul.innerHTML += li;
}
} else {
let li = `<li class="list-group-item">댓글이 없습니다.</li>`;
ul.innerHTML = li;
}
})
};
comment.js를 코드 뭉치별로 해석하자면 다음과 같다.
첫 번째 코드뭉치 : 댓글 등록 이벤트
document.getElementById('cmtPostBtn').addEventListener('click',()=>{
const cmtText = document.getElementById('cmtText');
if(cmtText.value == null || cmtText.value == '') {
alert('댓글을 입력해주세요');
cmtText.focus();
return false;
} else {
let cmtData = {
bno : bnoVal,
writer : document.getElementById('cmtWriter').innerHTML,
content : cmtText.value
};
console.log(cmtData);
// 댓글 등록
postCommentToServer(cmtData).then(result =>{
if(result == '1') {
console.log('댓글 등록 성공~!!');
cmtText.value = "";
}
// 화면에 뿌려
spreadCommentList(cmtData.bno);
})
}
});
cmtPostBtn이라는 id를 가진 버튼을 클릭했을 때,
document.getElementById('cmtPostBtn').addEventListener('click',()=>{
cmtText라는 id를 가지고 있는 정보를 들고와 그 정보를 js에서 cmtText라는 이름으로 설정한다.
const cmtText = document.getElementById('cmtText');
만약에, cmtText가 null이거나 비어있다면 댓글을 입력하라는 alert를 띄우고 cmtText로 focus를 맞춘다.
if(cmtText.value == null || cmtText.value == '') {
alert('댓글을 입력해주세요');
cmtText.focus();
return false;
}
cmtText는 jsp에서 댓글 입력을 하는 input의 id이므로 그 input에 focus가 맞추어져 커서가 깜빡거리게 된다.
비어있지 않다면 cmtData 라는 이름으로 정보를 저장할건데,
cmtData는 bno / writer / content의 정보를 담게 된다.
이 때, bno는 jsp에서 bnoVal로 js에 보낸다고 미리 선언해주었으므로, bno는 bnoVal의 값을 가지게 되고
writer는 cmtWriter이라는 id를 가진 정보를 가진다.
content는 앞서 가져온 cmtText의 값을 가진다.
else {
let cmtData = {
bno : bnoVal,
writer : document.getElementById('cmtWriter').innerHTML,
content : cmtText.value
};
저렇게 댓글의 정보가 담긴 cmtData를 postCommentToServer 함수에 담아 실행하는데,
result가 1이라면 댓글 등록에 성공한 것이고, cmtText를 비워준다.
cmtText를 비워주어야, 댓글을 입력하는 input 부분이 비워진다.
postCommentToServer(cmtData).then(result =>{
if(result == '1') {
console.log('댓글 등록 성공~!!');
cmtText.value = "";
}
그 후, cmtData의 bno값을 spreadCommentList 함수에 담아 댓글 리스트를 뿌린다.
spreadCommentList(cmtData.bno);
두 번째 코드뭉치 : 댓글 등록 함수
async function postCommentToServer(cmtData) {
try {
const url = "/comment/post";
const config = {
method : "post",
headers : {
'content-type' : 'application/json; charset=utf-8'
},
body : JSON.stringify(cmtData)
};
const resp = await fetch(url, config);
const result = await resp.text();
return result;
} catch (error) {
console.log(error);
}
};
postCommentToServer라는 이름을 가진 함수는 cmtData를 담고 실행될 건데,
async function postCommentToServer(cmtData) {
@RequestMapping이 (/comment/*)인 controller에서 /post의 경로로 실행된다.
try {
const url = "/comment/post";
이 함수의 method는 post이므로 controlle에서 @PostMapping으로 사용될 수 있고,
const config = {
method : "post",
데이터의 유형이 JSON이며, 문자 인코딩은 UTF-8을 사용한다.
headers : {
'content-type' : 'application/json; charset=utf-8'
},
cmtData 객체를 JSON 문자열로 변환하여 서버에 전달하여 서버가 적절하게 처리할 수 있게 한다.
body : JSON.stringify(cmtData)
};
http에 config 내용을 담은 url을 보내고 함수가 끝날 때 까지 기다린다.
const resp = await fetch(url, config);
그 후, 서버의 응답을 받은 resp의 내용을 text로 받아 result로 저장한다.
const result = await resp.text();
세 번째 코드뭉치 : 댓글 목록 조회 함수
위의 형태와 유사하므로 설명 생략
네 번째 코드뭉치 : 댓글 목록 업데이트 함수
function spreadCommentList(bno) {
getCommentListFromServer(bno).then(result=>{
console.log(result);
const ul = document.getElementById('cmtListArea');
ul.innerHTML = '';
if(result != null) {
for(let cvo of result) {
let li = `<li class="list-group-item" data-cno="${cvo.cno}" data-writer="${cvo.writer}">`;
li += `<div class="mb-3">`;
li += `<div class="fw-bold">${cvo.writer} `;
li += `<span class="badge rounded-pill text-bg-warning">${cvo.modAt}</span></div>`;
li += `${cvo.content}`;
li += `</div>`;
li += `<button type="button" class="btn btn-sm btn-outline-success cmtModBtn" data-bs-toggle="modal" data-bs-target="#myModal">Eidt</button>`;
li += `<button type="button" class="btn btn-sm btn-outline-danger cmtDelBtn">Delete</button>`;
li += `</li>`;
ul.innerHTML += li;
}
} else {
let li = `<li class="list-group-item">댓글이 없습니다.</li>`;
ul.innerHTML = li;
}
})
};
기초적인 부분을 제외하고 몇 가지만 골라서 설명하자면,
jsp에 댓글 리스트를 뿌릴 위치를 임의로 잡아준 코드를 덮기 위해서 비워준다.
ul.innerHTML = '';
for..of 구문은 배열의 요소를 뽑아 반복을 진행한다.
for(let cvo of result)
js 작성을 마쳤다면, detail.jsp에 댓글 리스트를 뿌리는 함수를 script를 추가한다.
<script type="text/javascript">
spreadCommentList(bnoVal);
</script>
detail.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<jsp:include page="../layout/header.jsp"></jsp:include>
<jsp:include page="../layout/nav.jsp"></jsp:include>
<div class="container-md">
// ... (기존 코드)
<!-- 댓글 등록 라인 -->
<div class="input-group mb-3">
<span class="input-group-text" id="cmtWriter">Writer</span>
<input type="text" class="form-control" id="cmtText" aria-label="Amount (to the nearest dollar)">
<button type="button" class="btn btn-success" id="cmtPostBtn">등록</button>
</div>
<!-- 댓글 표시 라인 -->
<ul class="list-group list-group-flush" id="cmtListArea">
<li class="list-group-item">
<div class="mb-3">
<div class="fw-bold">Writer <span class="badge rounded-pill text-bg-warning">modAt</span></div>
content
</div>
</li>
</ul>
</div>
<script type="text/javascript">
let bnoVal = `<c:out value="${bvo.bno}"/>`;
console.log(bnoVal);
</script>
<script src="/resources/js/boardComment.js"></script>
<script type="text/javascript">
spreadCommentList(bnoVal);
</script>
<jsp:include page="../layout/footer.jsp"></jsp:include>
CommentController.java
package com.basicWeb.www.controller;
import java.util.List;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.basicWeb.www.domain.CommentVO;
import com.basicWeb.www.service.CommentService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/comment/*")
public class CommentController {
private final CommentService csv;
@PostMapping(value = "/post", consumes = "application/json", produces = MediaType.TEXT_PLAIN_VALUE)
public ResponseEntity<String> post(@RequestBody CommentVO cvo) {
int isOk = csv.post(cvo);
return isOk > 0 ?
new ResponseEntity<String>("1", HttpStatus.OK) :
new ResponseEntity<String>("0", HttpStatus.INTERNAL_SERVER_ERROR);
}
@GetMapping(value="/{bno}", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<CommentVO>> list(@PathVariable("bno") long bno){
List<CommentVO> list = csv.getList(bno);
return new ResponseEntity<List<CommentVO>>(list, HttpStatus.OK);
}
}
각 Method 별로 간략하게 설명하자면 다음과 같다.
post() Method
@PostMapping(value = "/post", consumes = "application/json", produces = MediaType.TEXT_PLAIN_VALUE)
public ResponseEntity<String> post(@RequestBody CommentVO cvo) {
int isOk = csv.post(cvo);
return isOk > 0 ?
new ResponseEntity<String>("1", HttpStatus.OK) :
new ResponseEntity<String>("0", HttpStatus.INTERNAL_SERVER_ERROR);
}
post Method는 @PostMapping으로 실행하는데,
앞서 작성한 js와 함께 설명하자면 url이 /comment/post로 되어있고
headers가 json 형태인 데이터를 가져오는데,
text형태로 데이터를 내보내 http에 요청하는 함수를 사용해 실행한다는 것이다.
@PostMapping(value = "/post", consumes = "application/json", produces = MediaType.TEXT_PLAIN_VALUE)
post Method는 String 형태를 뱉어내는데,
body가 담고 있는 데이터를 CommentVO 형태의 객체를 만들어 cvo라는 이름을 부여한다.
public ResponseEntity<String> post(@RequestBody CommentVO cvo)
list() Method
@GetMapping(value="/{bno}", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<CommentVO>> list(@PathVariable("bno") long bno){
List<CommentVO> list = csv.getList(bno);
return new ResponseEntity<List<CommentVO>>(list, HttpStatus.OK);
}
json형태로 http에 요청하고 있고, CommentVO형식으로 List를 뱉어내는 Method이다.
@GetMapping(value="/{bno}", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<CommentVO>> list() {}
@PathVariable은 URL 경로에 포함된 값을 추출하여 메서드의 매개변수로 전달하는 어노테이션이다.
예를들어 /comment/list/123의 URL이 있다면,
123은 bno 변수로 URL 경로에서 추출되어 매개변수로 사용된다.
(@PathVariable("bno") long bno)
CommentService.interface
package com.basicWeb.www.service;
import java.util.List;
import com.basicWeb.www.domain.CommentVO;
public interface CommentService{
int post(CommentVO cvo);
List<CommentVO> getList(long bno);
}
CommentServiceImpl.java
package com.basicWeb.www.service;
import java.util.List;
import org.springframework.stereotype.Service;
import com.basicWeb.www.domain.CommentVO;
import com.basicWeb.www.repository.CommentDAO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Service
@RequiredArgsConstructor
public class CommentServiceImpl implements CommentService {
private final CommentDAO cdao;
@Override
public int post(CommentVO cvo) {
// TODO Auto-generated method stub
return cdao.insert(cvo);
}
@Override
public List<CommentVO> getList(long bno) {
// TODO Auto-generated method stub
return cdao.getList(bno);
}
}
CommentDAO.interface
package com.basicWeb.www.repository;
import java.util.List;
import com.basicWeb.www.domain.CommentVO;
public interface CommentDAO {
int insert(CommentVO cvo);
List<CommentVO> getList(long bno);
}
commnetMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.basicWeb.www.repository.CommentDAO">
<insert id="insert">
INSERT INTO comment (bno, writer, content)
VALUES (#{bno}, #{writer}, #{content})
</insert>
<select id="getList" resultType="com.basicWeb.www.domain.CommentVO">
SELECT * FROM comment
WHERE bno = #{bno}
ORDER BY cno DESC
</select>
</mapper>
<댓글 화면>
[Spring] 16. 댓글 - 생성 / 리스트
(다음 게시물 예고편)
[Spring] 17. 댓글 - 리스트 더보기 + 게시판 댓글 개수
얼렁뚱땅 주니어 개발자
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!