today_is
[ jsp 프로젝트 ] 게시글 저장 기능 (+ 대댓글) 본문
예정보다 빨리 끝나서 기획에는 없었던 게시글 저장 기능도 내가 진행하기로 했다

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'
}
기능 구현은 끝났다 !
'project' 카테고리의 다른 글
[ spring 프로젝트 ] oracle 계정 생성 (0) | 2024.03.06 |
---|---|
[ jsp 프로젝트 ] 프로젝트 회고 (0) | 2023.12.22 |
[ jsp 프로젝트 ] 게시글 작성 (+ FileUtil 을 이용한 이미지 업로드 / 페이징) (0) | 2023.12.21 |
[ jsp 프로젝트 ] 게시판 기능 (검색 / script 를 이용한 무한스크롤) (0) | 2023.12.20 |
[ jsp 프로젝트 ] 회원기능 (0) | 2023.12.18 |