본문 바로가기
Java/Servlet & JSP

JSP) 답글 폼 생성하기 / 이벤트 버블링을 이용한 유효성 검사

by 박채니 2022. 7. 6.

안녕하세요, 코린이의 코딩 학습기 채니 입니다.

 

개인 포스팅용으로 내용에 오류 및 잘못된 정보가 있을 수 있습니다.


답글 폼 생성하기

<section id="board-container">
	<h2>게시판</h2>
	<table id="tbl-board-view">
		<tr>
			<th>글번호</th>
			<td><%= board.getNo() %></td>
		</tr>
		<tr>
			<th>제 목</th>
			<td><%= board.getTitle() %></td>
		</tr>
		<tr>
			<th>작성자</th>
			<td><%= board.getWriter() %></td>
		</tr>
		<tr>
			<th>조회수</th>
			<td><%= board.getReadCount() %></td>
		</tr>
			<% if(attachments != null && !attachments.isEmpty()) {
				for(Attachment attach : attachments) { %>
		<tr>
			<th>첨부파일</th>
			<td>
				<%-- 첨부파일이 있을경우만, 이미지와 함께 original파일명 표시 --%>
				<img alt="첨부파일" src="<%=request.getContextPath() %>/images/file.png" width=16px>
				<a href="<%= request.getContextPath()%>/board/fileDownload?no=<%=attach.getNo()%>"><%= attach.getOriginalFilename() %></a>
			</td>
		</tr>
			<% 	}
			} %>
		<tr>
			<th>내 용</th>
			<td><%= board.getContent() %></td>
		</tr>
		<% 
		boolean canEdit = loginMember != null && (loginMember.getMemberId().equals(board.getWriter()) || loginMember.getMemberRole() == MemberRole.A);
		if(canEdit) { %>
		<tr>
			<%-- 작성자와 관리자만 마지막행 수정/삭제버튼이 보일수 있게 할 것 --%>
			<th colspan="2">
				<input type="button" value="수정하기" onclick="updateBoard()">
				<input type="button" value="삭제하기" onclick="deleteBoard()">
			</th>
		</tr>
		<% } %>
	</table>
	
	    <hr style="margin-top:30px;" />    
    
    <div class="comment-container">
    	<!-- 댓글 작성부 -->
        <div class="comment-editor">
            <form
            action="<%=request.getContextPath()%>/board/boardCommentEnroll" method="post" name="boardCommentFrm">
                <input type="hidden" name="boardNo" value="<%= board.getNo() %>" />
                <input type="hidden" name="writer" value="<%= loginMember != null ? loginMember.getMemberId() : "" %>" />
                <input type="hidden" name="commentLevel" value="1" />
                <input type="hidden" name="commentRef" value="0" />    
                <textarea name="content" cols="60" rows="3"></textarea>
                <button type="submit" id="btn-comment-enroll1">등록</button>
            </form>
        </div>
        <!--table#tbl-comment-->
        <table id="tbl-comment">
		<% if(commentList != null && !commentList.isEmpty()) {
			SimpleDateFormat sdf = new SimpleDateFormat("yy-MM-dd HH:mm");
			for(BoardComment comment : commentList) { %>        	
       		<tr class="<%= comment.getCommentLevel() == CommentLevel.COMMENT ? "level1" : "level2" %>">
       			<td>
       				<sub class="comment-writer"><%= comment.getWriter() %></sub>
       				<sub class="comment-date"><%= sdf.format(comment.getRegDate()) %></sub>
       				<div>
       					<%= comment.getContent() %>
       				</div>
       			</td>
       			<td>
       				<% if(comment.getCommentLevel() == CommentLevel.COMMENT) { %>
       				<button class="btn-reply" value="<%= comment.getNo() %>">답글</button>
       				<% } %>
       			</td>
        	</tr>
      			<% 	}
			} %>
        </table>
    </div>
</section>
<script>
document.querySelectorAll(".btn-reply").forEach((btn) => {
	btn.addEventListener('click', (e) => {
		<% if(loginMember == null) { %>
			loginAlert();
			e.preventDefault();
			return;
		<% } %>
		
		const {value} = e.target;
		const tr = `
		<tr>
			<td colspan="2" style="text-align:left;">
				<form
	           		action="<%=request.getContextPath()%>/board/boardCommentEnroll" method="post" name="boardCommentFrm">
	                <input type="hidden" name="boardNo" value="<%= board.getNo() %>" />
	                <input type="hidden" name="writer" value="<%= loginMember != null ? loginMember.getMemberId() : "" %>" />
	                <input type="hidden" name="commentLevel" value="2" />
	                <input type="hidden" name="commentRef" value="\${value}" />    
	                <textarea name="content" cols="60" rows="1"></textarea>
	                <button type="submit" class="btn-comment-enroll2">등록</button>
	            </form>
			</td>
		</tr>
		`;
		const target = e.target.parentElement.parentElement; // tr
		target.insertAdjacentHTML('afterend', tr);
	}, {once:true})
});

.btn-reply를 모두 가져와 forEach문을 통해 click이벤트가 발생하면 답글 폼을 제공해주었습니다.

답글이기 때문에 commentLevel은 '2'로 주었고, value값에 댓글의 no값을 주어 어떤 댓글에 대한 답글인지 정보를 주었습니다.

insertAdjacentHTML()의 'afterend'속성을 이용하여 댓글 tr태그 밑에 답글 tr태그를 추가해주었습니다.

 

또한, addEventListener의 once 옵션을 주어 한번만 실행되도록 해주었습니다.

https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentHTML

 

Element.insertAdjacentHTML() - Web APIs | MDN

The insertAdjacentHTML() method of the Element interface parses the specified text as HTML or XML and inserts the resulting nodes into the DOM tree at a specified position.

developer.mozilla.org

 


이벤트 버블링을 이용하여 추가한 답글폼에 유효성 검사 적용하기

- 이벤트 버블링 : 발생한 이벤트가 부모 방향으로 전파 (자식요소를 클릭해도 부모요소에서 핸들링 가능)

- 부모요소에서 자식 이벤트를 핸들링 할 수 있음

 

댓글/답글 폼 모두 동일한 이름을 가진 폼이지만, 위 사진과 같이 답글 폼에는 유효성검사가 적용되지 않은 것을 알 수 있습니다.

내용을 입력하지 않고 등록 버튼을 눌러도 폼이 제출되어 null이 써진 것을 확인할 수 있습니다.

document.boardCommentFrm.addEventListener('submit', (e) => {
	if(<%= loginMember == null%>) {
		loginAlert();
		e.preventDefault();
		return;
	}
	
	// addEventListener에서는 return false가 안됨! e.preventDefault()와 return을 조합해서 사용
	if(!/^(.|\n)+$/.test(e.target.content.value)) {
		alert("내용을 작성해주세요.");
		e.preventDefault();
		return;
	}
});

답글 폼은 페이지 로딩이 끝난 이후에 추가된 것이므로, 해당 이벤트 리스너가 적용되지 않는 것이죠.

동적으로 생성한 태그는 그때마다 핸들러를 추가해줘야 합니다.

 

번거롭기 때문에,

동적으로 생성한 태그들 또한 이벤트 버블링이 발생하는 것을 이용하여 처리해보겠습니다.

/**
 * 부모요소에서 자식 submit 이벤트 핸들링
 */
document.addEventListener('submit', (e) => {
	if(e.target.matches("form[name=boardCommentFrm]")) {
		if(<%= loginMember == null%>) {
			loginAlert();
			e.preventDefault();
			return;
		}
		
		// addEventListener에서는 return false가 안됨! e.preventDefault()와 return을 조합해서 사용
		if(!/^(.|\n)+$/.test(e.target.content.value)) {
			alert("내용을 작성해주세요.");
			e.preventDefault();
			return;
		}
	}
});

이벤트 버블링이 일어나서 위처럼 답글에도 유효성 검사가 적용되는 것을 확인할 수 있습니다.