today_is

[ jsp 프로젝트 ] 게시글 저장 기능 (+ 대댓글) 본문

project

[ jsp 프로젝트 ] 게시글 저장 기능 (+ 대댓글)

ye_rang 2023. 12. 21. 14:43

예정보다 빨리 끝나서 기획에는 없었던 게시글 저장 기능도 내가 진행하기로 했다

 

 

 

SavedDTO

IDX NOT NULL  NUMBER primary key
BOARD_IDX NOT NULL  NUMBER Board 의 idx 를 참조
MEMBERID NOT NULL  VARCHAR2(100) Member 의 userid 를 참조

 

 

SavedDAO

package saved;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;

import board.Paging;


public class SavedDAO {

	   private Context init;
	   private DataSource ds;
	   
	   private Connection conn;
	   private PreparedStatement pstmt;
	   private ResultSet rs;
	   
	   private static SavedDAO instance = new SavedDAO();
	   
	   public static SavedDAO getInstance() {
	      return instance;
	   }
	   
	   
	   private SavedDAO() {
	      try {
	         init = new InitialContext();
	         ds = (DataSource) init.lookup("java:comp/env/jdbc/oracle");
	      } catch (NamingException e) {
	         e.printStackTrace();
	      }
	   }
	   
	   private void close() {
	      try {
	         if(rs != null) rs.close();
	         if(pstmt != null) pstmt.close();
	         if(conn != null) conn.close();
	      } catch (SQLException e) {}
	   }
	   
	   private SavedDTO mapping(ResultSet rs) throws SQLException {
		   SavedDTO dto = new SavedDTO();
	      
	      dto.setIdx(rs.getInt("idx"));
	      dto.setBoard_idx(rs.getInt("board_idx"));
	      dto.setMemberId(rs.getString("memberId"));
	      
	      return dto;
	   }

	   //	게시글 별로 liked 를 0으로 만들어서 생성하기
	   public int likeInsert(int board_idx, String userid) {
		   int row = 0;
		   
		   String sql = "insert into saved(board_idx , memberId) values (?,?)";
		   
		   try {
			conn = ds.getConnection();
			pstmt = conn.prepareStatement(sql);
			pstmt.setInt(1, board_idx);
			pstmt.setString(2, userid);
			row = pstmt.executeUpdate();
			
		} catch (Exception e) {
			e.printStackTrace();
		}
		   finally {
			close();
		}
		   return row;
		   
	   }
	   
	  
	   //	좋아요가 있는지
	   public SavedDTO checkLike(int board_idx, String userid) {
		   SavedDTO dto = null;
		   
		   String sql = "select * from saved where board_idx = ? and memberId = ?";
		   
		   try {
			conn = ds.getConnection();
			pstmt = conn.prepareStatement(sql);
			
			pstmt.setInt(1, board_idx);
			pstmt.setString(2, userid);
			rs = pstmt.executeQuery();

			while(rs.next()) {
				dto = mapping(rs);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		   finally {
			close();
		}
		   return dto;
	   }
	   
	   
       //	저장한 게시글 목록 
	   public List<SavedDTO> savedList(String userid, savedPaging paging) {
		   ArrayList<SavedDTO> list = new ArrayList<>();
		   
		   String sql = "select b.idx as board_idx, b.title, b.writer, c.idx"
		   		+ "    from board b"
		   		+ "    join saved c"
		   		+ "        on b.idx = c.board_idx"
		   		+ "    where c.memberId = ?"
		   		+ "        order by idx desc"
		   		+ "   offset ? rows "
	            + "   fetch next ? rows only";
		   
		   try {
			conn = ds.getConnection();
			pstmt = conn.prepareStatement(sql);
			pstmt.setString(1, userid);
	        pstmt.setInt(2, paging.getOffset());
	        pstmt.setInt(3, paging.getFetch());
			rs = pstmt.executeQuery();
			
			  while(rs.next()) {
				SavedDTO dto = new SavedDTO();
				dto.setIdx(rs.getInt("idx"));
				dto.setBoard_idx(rs.getInt("board_idx"));
				dto.setTitle(rs.getString("title"));
				dto.setWriter(rs.getString("writer"));
				
				list.add(dto);
			  }
		} catch (Exception e) {
				e.printStackTrace();
		}
		  finally {
			close();
		}
		   return list;
	   }
	   
	   
	   
	   // 내가 즐겨찾기한 게시글 개수 불러오는 함수
	   public int SavedCount(String userid) {
	      int count = 0;
	      String sql = "select count(*) from saved"
	      		+ " where memberId = ?"
	      		+ " order by idx desc";

	      try {
	         conn = ds.getConnection();
	         pstmt = conn.prepareStatement(sql);
	         pstmt.setString(1, userid);
	         rs = pstmt.executeQuery();
	         while (rs.next()) {
	            count = rs.getInt(1);
	         }
	      } catch (SQLException e) {
	         e.printStackTrace();
	      } finally {close();}
	      return count;
	   }
}

 

 

SavedPaging

: 내가 저장한 게시글의 목록을 페이징해서 보여줌

package saved;

public class savedPaging {
	
	// 요청받은 페이지
	private int page;
	// 페이지 당 출력할 게시글의 수
	private int perPage;
	// 총 게시글의 개수 (삭제, 검색 반영)
	private int boardCount;
	// sql에서 사용할 건너뛸 개수의 값
	private int offset;
	// 건너 뛴 후 불러올 게시글의 수 
	private int fetch;
	
	
	// 전체 페이지 개수 
	private int pageCount;
	// 페이지를 n개씩 묶어 하나의 구역으로 설정 후 0부터 시작
	private int section;
	// 이전 구역이 있다면 true
	private boolean prev;
	// 다음 구역이 있다면 true
	private boolean next;
	// 구역의 시작 페이지
	private int begin;
	// 구역의 끝 페이지 (단, 끝 페이지가 pageCount보다 크면 안된다)
	private int end;
	
	
	
	
	// 생성자를 대신하는 static method
	public static savedPaging newInstance(int page, int boardCount) {
		return new savedPaging(page, boardCount);
	}
	
	private savedPaging(int page, int boardCount) {
		this.page = page;
		this.boardCount = boardCount;
		
		perPage = 10;
		offset = (page -1) * perPage;
		fetch = perPage;
		
		pageCount = boardCount /perPage;
		pageCount += (boardCount % perPage != 0) ? 1 :0;
		section = (page -1) / 10;
		begin = section * 10 + 1;
		end = begin + 9;
		prev = section != 0;
		next = pageCount > end;
		
		if (end > pageCount) {
			end = pageCount;
			next = false;
		}
	}
	
	
	public int getPage() {
		return page;
	}
	public void setPage(int page) {
		this.page = page;
	}
	public int getPerPage() {
		return perPage;
	}
	public void setPerPage(int perPage) {
		this.perPage = perPage;
	}
	public int getBoardCount() {
		return boardCount;
	}
	public void setBoardCount(int boardCount) {
		this.boardCount = boardCount;
	}
	public int getOffset() {
		return offset;
	}
	public void setOffset(int offset) {
		this.offset = offset;
	}
	public int getFetch() {
		return fetch;
	}
	public void setFetch(int fetch) {
		this.fetch = fetch;
	}
	public int getPageCount() {
		return pageCount;
	}
	public void setPageCount(int pageCount) {
		this.pageCount = pageCount;
	}
	public int getSection() {
		return section;
	}
	public void setSection(int section) {
		this.section = section;
	}
	public boolean isPrev() {
		return prev;
	}
	public void setPrev(boolean prev) {
		this.prev = prev;
	}
	public boolean isNext() {
		return next;
	}
	public void setNext(boolean next) {
		this.next = next;
	}
	public int getBegin() {
		return begin;
	}
	public void setBegin(int begin) {
		this.begin = begin;
	}
	public int getEnd() {
		return end;
	}
	public void setEnd(int end) {
		this.end = end;
	}

}

 

 

 

게시글 상세보기 - view.jsp

 

 

게시글 저장 버튼

: c:if 를 이용하여 로그인이 된 상태에만

게시글 저장 버튼이 보이도록 함

 

 

대댓글 출력

: 대댓글은 댓글의 깊이를 이용해서 표현하고자 함

 

reply_depth 는 초기값이 0

 

대댓글일때에는 margin left 를 이용해서 빈공간을 만들어서, 계단식 댓글로 표현 

 

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ include file="header.jsp" %>

   
   <div class="frame">
     <c:set var="dto" value="${boardDAO.selectOne(param.idx) }"></c:set>
   	 
     <div class="flex" id="boardView"> 
    	<div class="viewimage"><img  src="${cpath }/reviewImage${dto.image }"></div>
      	<div class="viewboard">       
            <p class="title">${dto.title }</p>
            <p class="content">${dto.content }</p>
      	</div>
     </div>

     <c:if test="${login.userid == dto.writer}">
     	<a class="deleteBtn" idx="${dto.idx }" href="${cpath }/boardDelete.jsp?idx=${dto.idx }"><button>삭제</button></a>
        <a href="${cpath }/boardUpdate.jsp?idx=${dto.idx }"><button>수정</button></a>
     </c:if>
         
     <c:if test="${not empty login }">
        <a href="${cpath }/savePost.jsp?board_idx=${dto.idx }"><button>저장</button></a>
     </c:if>
         
   </div>
     
   <div class="topSpace">
      <c:if test="${not empty login }">
        <form method="POST" id="replyWriteForm">
          	<p><textarea class="viewtext" name="content" placeholder="댓글 작성" rows="8" cols="100"></textarea></p>
           	<p><input type="hidden" name="board_idx" value="${dto.idx }"></p>
           	<p><input type="hidden" name="writer" value="${login.userid }"></p>
        	<p><input type="hidden" name="parent_idx" value="0"></p>
           	<p><input type="hidden" name="reply_depth" value="0"></p>
           	<p><input type="submit" value="댓글등록"></p>   
        </form>
        
        <c:if test="${pageContext.request.method == 'POST' }">
           <jsp:useBean id="reply" class="reply.ReplyDTO" />
           <jsp:setProperty property="*" name="reply" />
           <c:set var="row" value="${replyDAO.insert(reply) }" />
           <c:redirect url="/view.jsp?idx=${dto.idx }"></c:redirect>
        </c:if>
        
      </c:if>
    
     
      <div class="replyList">
        <c:forEach var="reply" items="${replyDAO.selectreplyList(dto.idx) }">
          <div class=" replyItem" 
              idx="${reply.idx }" reply_depth="${reply.reply_depth }"
              style="border: 1px solid grey; 
              padding: 10px; 
              margin: -1px; 
              margin-left: ${reply.reply_depth * 30}px">
              <p>${reply.writer }</p>
              <pre>${reply.content }</pre>
              <button class="replyBtn" onclick="toggleReplyForm(this)">답글달기</button>
           </div>
         </c:forEach>
      </div>
    </div>     
    
    <c:if test="${login.userid == list.writer }">
    	<a href="${cpath }/replyDelete.jsp?idx=${list.idx }&board_idx=${list.board_idx }"><button>삭제</button></a>
    </c:if>

   
	<script type="text/javascript">
   		function toggleReplyForm(btn) {
       	const form = document.getElementById('replyWriteForm');
       	const reply = btn.parentNode;

      
      	form.querySelector('input[name="parent_idx"]').value = reply.getAttribute('idx')
      	form.querySelector('input[name="reply_depth"]').value = +reply.getAttribute('reply_depth') + 1
      
      	reply.style.border = '2px solid orange'
      	reply.appendChild(form)
      	form.querySelector('textarea').focus()
      
      	btn.style.display = 'none'
      
  		}
   
   replyBtnList.forEach(e => e.onclick = replyBtnHandler)
   
   const replyItemList = document.querySelectorAll('div.replyItem')
   
   replyItemList.forEach((div, index) => {
      const depth = div.getAttribute('reply_depth')
      if(depth == 0){
         div.style.marginTop = '10px'
         div.style.background = 'white'
      }
      else if(index != 0) {
         const preDiv = replyItemList[index -1]
         const preDepth = prevDiv.getAttribute('reply_depth')
         if(depth - prevDepth == 1){
            
         const line = document.createElement('div')
         line.innerText = '⨽'
         div.style.position = 'relative'
         line.style.postion = 'absolute'
         line.style.top = '0'
         line.style.left = '-35px'
         div.appendChile(line)
         }
      }
   })

</script>
   
   
</body>
</html>

 

 

 

댓글 목록 함수 (ReplyDAO)

//   댓글 목록   : 원본 게시글 번호를 전달받아서 목록 반환
   public List<ReplyDTO> selectreplyList(int board_idx) {
      ArrayList<ReplyDTO> list = new ArrayList<>();
      
      String sql = "select * from reply "
      		+ "where board_idx = ?"
      		+ " start with parent_idx = 0"
      		+ " connect by prior idx = parent_idx"
      		+ " order SIBLINGS by idx";
      
      try {
         conn = ds.getConnection();
         pstmt = conn.prepareStatement(sql);
         pstmt.setInt(1, board_idx);
         rs = pstmt.executeQuery();
         while(rs.next()) {
            ReplyDTO dto = new ReplyDTO();
            
            dto.setBoard_idx(rs.getInt("board_idx"));
            dto.setParent_idx(rs.getInt("parent_idx"));
            dto.setIdx(rs.getInt("idx"));
            dto.setReply_depth(rs.getInt("reply_depth"));
            dto.setWriter(rs.getString("writer"));
            dto.setContent(rs.getString("content"));
            dto.setWriteDate(rs.getDate("writeDate"));
            
            list.add(dto);
         }
         
      } catch (Exception e) {
         e.printStackTrace();
      }
      finally {
         close();
      }
      return list;
   }

내가 저장한 게시글 보기 - mySaved.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ include file="header.jsp" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>

    <c:set var="savedCount" value="${savedDAO.SavedCount(login.userid) }"></c:set>
    <c:set var="paramPage" value="${empty param.page ? 1 : param.page }"></c:set>
    <c:set var="paging" value="${savedPaging.newInstance(paramPage, savedCount) }"></c:set>
    <c:set var="list" value="${savedDAO.savedList(login.userid, paging) }"></c:set> 

   <table id="mypageList">
      <thead>
         <tr>
            <th>글 번호</th>
            <th>제목</th>
            <th>작성자</th>
         </tr>
       
      </thead>
      <tbody>
      
<!--  	변수 출력확인용
         <li>게시글 개수 : ${boardCount }</li>
         <li>요청받은 페이지 : ${paramPage }</li>
         <li>paging.offset : ${paging.offset }</li>
         <li>paging.fetch : ${paging.fetch }</li>
         <li>paging.pageCount : ${paging.pageCount }</li>
         <li>paging.begin : ${paging.begin }</li>
         <li>paging.end : ${paging.end }</li>
-->


         <c:forEach var="dto" items="${list }">
            <tr class="clickable-row" data-href="${cpath}/view.jsp?idx=${dto.board_idx}">
           
               <td>${dto.board_idx }</td>
               <td>${dto.title }</td>
               <td>${dto.writer }</td>
           
            </tr>
         </c:forEach>
      </tbody>
   </table>
   
   <script>
	         document.addEventListener("DOMContentLoaded", function () {
	             var rows = document.querySelectorAll(".clickable-row");
	             rows.forEach(function (row) {
	                 row.addEventListener("click", function () {
	                     var href = row.getAttribute("data-href");
	                     if (href) {
	                         window.location.href = href;
	                     }
	                 });
	             });
	         });
	</script>

   
   <div class="frame mypageing">
      <c:if test="${paging.prev }">
         <a href="${cpath }/mysaved.jsp?page=${paging.begin - 10}">[이전]</a>
      </c:if>
      
      <c:forEach var="i" begin="${paging.begin }" end="${paging.end }">
         <a class="${paging.page == i ? 'bold' : ''}"
         href="${cpath }/mysaved.jsp?page=${i}">[${i }]</a>
      </c:forEach>
      
      <c:if test="${paging.next }">
         <a href="${cpath }/mysaved.jsp?page=${paging.end + 1}">[다음]</a>
      </c:if>
   </div>


</body>
</html>

 

 


 

Review

어려웠던 부분

: 대댓글을 구현하는데 있어서 오래걸렸다

 

 

대댓글이 제대로 되는지 확인해본다고 생성을 많이했더니,

어떤게 첫번째 댓글인지 구별하기도 어려웠었다

 

그래서 첫번째 댓글일때는 스타일을 다르게 주었다

if(depth == 0){
   div.style.marginTop = '10px'
   div.style.background = 'white'
}

 

 

기능 구현은 끝났다 !