커뮤니티 - 댓글 목록 조회 기능 구현 (23.07.20)

·2023년 7월 20일
0

Server

목록 보기
25/35
post-thumbnail

📝 댓글 목록 조회 기능


💡 VS Code

🔎 boardDetail.jsp

...
    <!-- jQuery 추가 -->
    <script src="https://code.jquery.com/jquery-3.7.0.min.js" integrity="sha256-2Pmvv0kuTBOenSvLm6bvfBSSHrUJ+3A7x6P5Ebd07/g=" crossorigin="anonymous"></script>

    <script src="${contextPath}/resources/js/board/board.js"></script>


    <script>
        // 댓글 관련 JS 코드에 필요한 값을 전역 변수로 선언

        // jsp 파일 : html, css, js, el, jstl 사용 가능
        // js  파일 : js만 사용 가능

        // 코드 해석 순서 : EL == JSTL > HTML > JS

        // ** JS 코드에서 EL/JSTL을 작성하게 된다면 반드시 ""를 양쪽에 추가 **

        // 최상위 주소
        const contextPath = "${contextPath}";

        // 게시글 번호
        const boardNo = "${detail.boardNo}"; // "500"

        // 로그인한 회원 번호
        const loginMemberNo = "${loginMember.memberNo}";
        // -> 로그인 0 : "10";
        // -> 로그인 X : ""; (빈 문자열)
    </script>


    <script src="${contextPath}/resources/js/board/reply.js"></script>

</body>
</html>

🔎 reply.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="reply-area">
    <!-- 댓글 목록 -->
    <div class="reply-list-area">

        <ul id="reply-list">

            <c:forEach var="reply" items="${rList}">

                <li class="reply-row">

                    <p class="reply-writer">
                        <c:if test="empty reply.profileImage">
                            <!-- 프로필 이미지가 없을 경우 -->
                            <img src="${contextPath}/resources/images/user.png">
                        </c:if>

                        <c:if test="!empty reply.profileImage">
                            <!-- 프로필 이미지가 없을 경우 -->
                            <img src="${contextPath}${reply.profileImage}">
                        </c:if>

                        <span>${reply.memberNickname}</span>
                        <span class="reply-date"> ${reply.createDate} </span>
                    </p>
                        
                    <p class="reply-content">${reply.replyContent}</p>
    
                    <c:if test="${loginMember.memberNo == reply.memberNo}">
                        <div class="reply-btn-area">
                            <button>수정</button>
                            <button>삭제</button>
                        </div>
                    </c:if>

                </li>

            </c:forEach>

            <li class="reply-row">
                <p class="reply-writer">
                    <img src="${contextPath}/resources/images/user.png">
                    <span>댓글 작성자 닉네임</span>
                    <span class="reply-date"> (2023.07.20 10:20:30) </span>
                </p>
                    
                <p class="reply-content">
                    댓글 내용입니다.<br>
                    이런식으로 출력 예정!
                </p>

                <div class="reply-btn-area">
                    <button>수정</button>
                    <button>삭제</button>
                </div>
            </li>

        </ul>

    </div>

    <!-- 댓글 작성 부분 -->

    <div class="reply-write-area">
        <textarea id="replyContent"></textarea>
        <button id="addReply">
            댓글<br>
            등록
        </button>
    </div>

</div>

🔎 reply-style.css

/* 댓글 목록(ul) */
#reply-list{
    padding: 0;
    list-style: none;
}

/* 댓글 한 줄(댓글 하나) */
.reply-row{
    padding: 15px 30px;
    border-top: 1px solid #ccc;
}

/* 댓글 작성자 프로필 이미지 */
.reply-writer > img{
    width: 40px;
    height: 40px;
}

/* 댓글 작성자 닉네임, 작성일 */
.reply-writer > span{
    font-weight: bold;
    margin-left: 10px;
}

/* 댓글 작성일 */
.reply-date{
    font-size: 0.8em;
    color: #aaa;
}

/* 버튼 영역 */
.reply-btn-area{
    display: flex;
    justify-content: flex-end;
}

/* 버튼 */
.reply-btn-area > button{
    width: 50px;
    height: 30px;
    margin-left: 10px;

    font-weight: bold;
    color: white;
    background-color: #455ba8;
    border: 0;
    border-radius: 3px;

    cursor: pointer;
}

/* 버튼 hover */
.reply-btn-area > button:hover{
    background-color: white;
    border: 2px solid #455ba8;
    color: #455ba8;
}

/* 댓글 작성 영역 */
.reply-write-area{
    display: flex;
    justify-content: center;
    min-height: 85px;
    margin: 30px 0;
}

/* 댓글 작성 내용 */
#replyContent{
    resize: none;
    margin-right: 10px;
    flex-basis: 75%;
}

/* 댓글 등록 버튼 */
#addReply{
    flex-basis: 8%;
    font-weight: bold;
    background-color: #455ba8;
    color: white;
    border: 0;
    cursor: pointer;
}

#addReply:hover{
    background-color: white;
    color: #455ba8;
    border: 2px solid #455ba8;
}

🔎 reply.js

// selectReplyList();

// 댓글 목록 조회(AJAX)
function selectReplyList(){

    // contextPath, boardNo, memberNo 전역 변수 사용
    $.ajax({

        url : contextPath + "/reply/selectReplyList",
        data : {"boardNo" : boardNo},
        type : "get",
        dataType : "JSON", // JSON 형태의 문자열 응답 데이터를 JS 객체로 자동 변환
        success : function(rList){
            // rList : 반환받은 댓글 목록
            console.log(rList);

            // 화면에 출력되어 있는 댓글 목록 삭제
            const replyList = document.getElementById("reply-list"); // ul태그
            replyList.innerHTML = "";

            // rList에 저장된 요소를 하나씩 접근
            for(let reply of rList){
                
                // 행
                const replyRow = document.createElement("li");
                replyRow.classList.add("reply-row");

                // 작성자
                const replyWriter = document.createElement("p");
                replyWriter.classList.add("reply-writer");

                // 프로필 이미지
                const profileImage = document.createElement("img");

                if(reply.profileImage != null){ // 프로필 이미지가 있는 경우

                    profileImage.setAttribute("src", contextPath + reply.profileImage);

                } else{ // 없는 경우 == 기본 이미지
                    profileImage.setAttribute("src", contextPath + "/resources/images/user.png");
                }

                // 작성자 닉네임
                const memberNickname = document.createElement("span");
                memberNickname.innerText = reply.memberNickname;

                // 작성일
                const replyDate = document.createElement("span");
                replyDate.classList.add("reply-date");
                replyDate.innerText = "(" + reply.createDate + ")";

                // 작성자 영역(p)에 프로필, 닉네임, 작성일 마지막 자식으로 추가
                replyWriter.append(profileImage, memberNickname, replyDate);

                // 댓글 내용
                const replyContent = document.createElement("p");
                replyContent.classList.add("reply-content");

                // 왜 innerHTML? <br> 태그 인식을 위해서
                replyContent.innerHTML = reply.replyContent;

                // 행에 작성자, 내용 추가
                replyRow.append(replyWriter, replyContent);

                // 로그인한 회원 번호와 댓글 작성자의 회원 번호가 같을 때만 버튼 추가
                if(loginMemberNo == reply.memberNo){

                    // 버튼 영역
                    const replyBtnArea = document.createElement("div");
                    replyBtnArea.classList.add("reply-btn-area");
                    
                    // 수정 버튼
                    const updateBtn = document.createElement("button");
                    updateBtn.innerText = "수정";
    
                    // 삭제 버튼
                    const deleteBtn = document.createElement("button");
                    deleteBtn.innerText = "삭제";
    
                    // 버튼 영역 마지막 자식으로 수정/삭제 버튼 추가
                    replyBtnArea.append(updateBtn, deleteBtn);

                    // 행에 버튼 영역 추가
                    replyRow.append(replyBtnArea);
                }
                    
                // 댓글 목록(ul)에 행(li) 추가
                replyList.append(replyRow);
            }
        },

        error : function(){
            console.log("에러 발생");
        }
    });
}

💡 Eclipse

🔎 boardDetailServlet.java

package edu.kh.community.board.controller;

import java.io.IOException;
import java.util.List;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import edu.kh.community.board.model.service.BoardService;
import edu.kh.community.board.model.service.ReplyService;
import edu.kh.community.board.model.vo.BoardDetail;
import edu.kh.community.board.model.vo.Reply;

@WebServlet("/board/detail")
public class BoardDetailServlet extends HttpServlet {

	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

		try {
			// 파라미터 중 게시글 번호(no) 얻어오기
			int boardNo = Integer.parseInt(req.getParameter("no"));
			
			BoardService service = new BoardService();
			
			// 게시글 정보 + 이미지리스트 조회
			BoardDetail detail = service.selectBoardDetail(boardNo);
			
			// 게시글 상세 조회된 내용이 있을 경우 댓글 목록 조회
			if(detail != null) {
				
				List<Reply> rList = new ReplyService().selectReplyList(boardNo);
				req.setAttribute("rList", rList);
			}
			
			req.setAttribute("detail", detail);
			
			String path = "/WEB-INF/views/board/boardDetail.jsp";
			RequestDispatcher dispatcher = req.getRequestDispatcher(path);
			
			dispatcher.forward(req, resp);
			
		} catch (Exception e) {
			e.printStackTrace();
		}
	
	}
	
}

🔎 ReplyController.java

package edu.kh.community.board.controller;

import java.io.IOException;
import java.util.List;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.google.gson.Gson;

import edu.kh.community.board.model.service.ReplyService;
import edu.kh.community.board.model.vo.Reply;

// Controller : 요청에 따라 알맞은 서비스를 호출하고
// 				요청 처리 결과를 내보내 줄(응답할) view를 선택

// *** Front Controller 패턴 ***
// 하나의 Servlet이 여러 요청을 받아들이고 제어하는 패턴

@WebServlet("/reply/*") // reply로 시작하는 모든 요청을 받음
public class ReplyController extends HttpServlet {
	
	// /reply/selectReplyList
	// /reply/insert
	// /reply/update
	// /reply/delete
	
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		// GET방식 요청 처리
		String uri = req.getRequestURI();
		String contextPath = req.getContextPath();
		String command = uri.substring(  (contextPath + "/reply/").length()  );
					
		ReplyService service = new ReplyService();

		try {
			// 댓글 목록 조회 요청인 경우
			if(command.equals("selectReplyList")) {
				
				// 파라미터를 얻어와 정수 형태로 파싱
				int boardNo = Integer.parseInt(req.getParameter("boardNo"));
				
				// 댓글 목록 조회 서비스 호출 후 결과 반환 받기
				List<Reply> rList = service.selectReplyList(boardNo);
				
				// JSON 변환 + 응답
				new Gson().toJson(rList, resp.getWriter());

			}

		} catch (Exception e) {
				e.printStackTrace();
		}
	
	}
	
	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		// POST방식 요청 처리
		doGet(req, resp); // POST로 전달된 요청을 doGet()으로 전달하여 수행
	}

}

🔎 ReplyService.java

package edu.kh.community.board.model.service;
import static edu.kh.community.common.JDBCTemplate.*;

import java.sql.Connection;
import java.util.ArrayList;
import java.util.List;

import edu.kh.community.board.model.dao.ReplyDAO;
import edu.kh.community.board.model.vo.Reply;

public class ReplyService {

	private ReplyDAO dao = new ReplyDAO();

	/** 댓글 목록 조회 Service
	 * @param boardNo
	 * @return rList
	 * @throws Exception
	 */
	public List<Reply> selectReplyList(int boardNo) throws Exception {

		Connection conn = getConnection(); // DBCP에서 커넥션 얻어오기

		// 댓글 목록 조회 SQL 수행 후 결과 반환 받기
		List<Reply> rList = dao.selectReplyList(conn, boardNo);
			
		close(conn);
		
		return rList;
	}


}

🔎 ReplyDAO.java

package edu.kh.community.board.model.dao;

import static edu.kh.community.common.JDBCTemplate.*;

import java.io.FileInputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

import edu.kh.community.board.model.vo.Reply;

public class ReplyDAO {
	
	private Statement stmt;
	private PreparedStatement pstmt;
	private ResultSet rs;
	
	private Properties prop;
	
	public ReplyDAO() {
		
		try {
			prop = new Properties();
			
			String filePath = ReplyDAO.class.getResource("/edu/kh/community/sql/reply-sql.xml").getPath();
			
			prop.loadFromXML(new FileInputStream(filePath));
					
		} catch (Exception e) {
			e.printStackTrace();
		}
		
	}

	/** 댓글 목록 조회 DAO
	 * @param conn
	 * @param boardNo
	 * @return rList
	 * @throws Exception
	 */
	public List<Reply> selectReplyList(Connection conn, int boardNo) throws Exception {
		
		List<Reply> rList = new ArrayList<Reply>();
		
		try {
			String sql = prop.getProperty("selectReplyList");
			
			pstmt = conn.prepareStatement(sql);
			
			pstmt.setInt(1, boardNo);
			
			rs = pstmt.executeQuery();
			
			while(rs.next()) {
				
				Reply r = new Reply();
				
				r.setReplyNo( rs.getInt(1) );
				r.setReplyContent( rs.getString(2) );
				r.setCreateDate( rs.getString(3) );
				r.setBoardNo( rs.getInt(4) );
				r.setMemberNo( rs.getInt(5) );
				r.setMemberNickname( rs.getString(6) );
				r.setProfileImage( rs.getString(7) );
			
				rList.add(r);
			}
			
		} finally {
			close(rs);
			close(pstmt);
		}
		
		return rList;
	}
}

🔎 reply-sql.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>reply-sql.xml</comment>

	<!-- 댓글 목록 조회 -->
	<entry key="selectReplyList">
		SELECT REPLY_NO, REPLY_CONTENT,
		    TO_CHAR(CREATE_DT, 'YYYY.MM.DD HH24:MI:SS') CREATE_DT,
		    BOARD_NO, MEMBER_NO, MEMBER_NICK, PROFILE_IMG
		FROM REPLY
		JOIN MEMBER USING(MEMBER_NO)
		WHERE REPLY_ST = 'N'
		AND BOARD_NO = ?
		ORDER BY REPLY_NO
	</entry>

</properties>

profile
풀스택 개발자 기록집 📁

1개의 댓글

comment-user-thumbnail
2023년 7월 20일

소중한 정보 감사드립니다!

답글 달기