타임리프로 찜쪄먹기


타임리프 잎파리~

찜쪄버려.

저번글에서는 기본적인 도메인/비즈니스 로직을 수정하고 컨트롤러에 필요한 메소드를 추가했다.

이제는 추가한 기능들을 뷰에서 사용하기 위해 타임리프로 찜쪄먹어보자.

필요한 문법

타임리프에서 사용할 메소드는 댓글을 출력할때 필요한 반복문 th:each 와 답글을 입력할 폼을 출력해줄때 각 태그마다 아이디를 부여해줄 th:id , 그리고 조건문 th:if 이다.

일단 가장 먼저 작성해야할 것들은 답글을 입력할 창이 있어야하기 때문에 폼을 하나 만들어주자.

여기서 부트스트랩의 콜랩스를 사용할것이다.

우리가 흔히 아는 펼치기 접기이다.
(답글 입력 버튼을 누르면 갑자기 입력창이 뙇 하고 생기거나 하는부분은 완전히 프론트 쪽이라고 생각하기 때문에 콜랩스를 사용했다.)

일단 부트스트랩과 콜랩스를 사용하기 위해 필요한 소스를 붙여넣기 해주자.

<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js" integrity="sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.12.9/dist/umd/popper.min.js" integrity="sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.0.0/dist/js/bootstrap.min.js" integrity="sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl" crossorigin="anonymous"></script>

해당 스크립트 태그는 /body 태그 앞에 붙여주고,

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.0.0/dist/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">

이거는 head 태그 안에 붙여주면 된다.

구현해보기

일단 기존에 작성해두었던 댓글 출력은 답글 출력과도 방법이 비슷하고, 답글달기 로직은 댓글수정 로직과도 비슷하다.

각 댓글 칸마다 원하는 동작을 수행하는 폼들이 열려야하고 그거를 콜랩스로 해결할것이다.

일단 원하는 위치에 버튼을 생성하고 해당 태그 안에 부모 댓글의 아이디를 입력해줄 히든 타입의 인풋 태그를 넣어주면 된다.

부트스트랩의 콜랩스는 단순히 펼치기 댓글의 버튼태그에 data-toggle 로 콜랩스를 입력해주고, 펼침을 당할 타겟을 data target 으로 지정해주면 된다.

여기서 th:id 와 th:attr 문법을 사용해줄것이다.

th:attr 문법은 속성을 추가할때 사용하는 문법이고, 단순히 data-target 으로는 원하는 뷰가 출력이 안되어서 스택오버플로우에서 찾아낸 방법이다.

       <button type="button"
         data-bs-toggle="collapse"
         sec:authorize="isAuthenticated()"
         th:attr="data-bs-target=|#r${articleComment.id}"
         th:if="${articleComment.userId != null and articleComment.userId.equals(#authentication.getName())}"
         style="display:inline" class="btn btn-light">답글달기
       </button>

이렇게 버튼 태그 안에 data-toggle 로 collapse 를 입력하고, 당연히 로그인 되어있는 상태에서 댓글을 등록할 수 있기 때문에 sec:authorize="isAuthenticated()" 로 로그인 되어있을때만 버튼을 보여주게 설정해준다.

그리고 대망의 data-target .. 입력 폼을 출력할 태그의 아이디를 부여해 속성을 추가해주면 된다. (해당 태그대로 입력하지 않으면 답글을 달 수 있는 모든 댓글에 답글입력 폼이 등장한다.. 바로 이렇게..)

[스프링] ajax 활용 안하고 댓글 수정 기능 구현하기

댓글 수정과 비슷한 로직으로 뷰를 작성했다.

그리고 대댓글 입력폼을 원하는 위치에 만들어주면 된다. 물론 th:id 로 각각의 폼에 고유 아이디를 부여해줘야한다.

    <div class="collapse" th:id="r + ${articleComment.id}">
       <form sec:authorize="isAuthenticated()"  th:action="@{|/articles/comments/${article.id}/reply|}" method="post" th:object="${dto}">
       <div class="input-group" style="width:auto">
       <label for="content" class="form-label mt-4" hidden>댓글 작성</label>
      <input type="text"  th:name="content" class="form-control" id="reply1" name="reply1">
      <input type="hidden" th:name="parentId" th:value="${articleComment.id}"/>
      <button type="submit" class="btn btn-outline-dark">답글작성</button>
      <br>
     </div>
   <br>
  </form>
</div>

이렇게 해당 폼에 action 으로 미리 생성해둔 post 대댓글 메소드의 url 을 넣어주고 object로 ArticleCommentRequestDto 객체를 전달해준다. (해당 객체는 modelMap 에 넣어서 전달해주면 된다. 사실 해당 객체는 validation 을 위한 객체로 전달해주었는데 이 포스트에선 사용하지 않기때문에 없어도 된다.)

대댓글 입력해보기

그러면 이제 작성한 뷰에서 대댓글을 입력해보자.

이렇게 답글달기 버튼을 누르면 입력할 수 있는 폼이 등장한다.

원하는 대댓글을 입력하고 답글을 작성하면 ??

아직은 출력부분을 제대로 손보지 않았고, 대댓글 또한 단순 댓글로 인식하기 때문에 이렇게 그냥 댓글인것처럼 출력이 될것이다.

하지만 IDEA 의 데이터베이스 부분을 살펴보면 대댓글로 등록이 된 것을 볼 수 있다.

이렇게 부모댓글이 맞는지 아닌지 정확하게 입력이 되어있고, 2번글의 6번 댓글의 대댓글로 등록이 되었다.

대댓글 출력하기

이제 대망의 대댓글을 출력할 차례다.

단순히 기존의 부모댓글의 출력부분 밑에 대댓글 출력 부분을 단순히 몇가지만 수정해서 붙여넣기 해주면 된다.

    <div th:each="reply : ${articleComment.children}">
        <div class="children" th:if="${reply.deleted != 'Y' }">
      &nbsp;
       <time class="comment4" th:datetime="${reply.createdAt}"
       th:text="${#temporals.format(reply.createdAt, 'yyyy-MM-dd HH:mm')}"
        style="display:inline"><small>2022-10-01</small></time>
      <img src="/img/reply-ico.png" alt="답글" style="display:inline" class="replyicon">
      <strong class="writer" th:text="${reply.userAccountDto.nickname}">brince</strong>
      &nbsp;
      
     <p th:text="${reply.content}">
      <p>
     <hr>
     </div>
     <div class="children" th:if="${reply.deleted == 'Y'}">
      <p >
      <img src="/img/reply-ico.png" alt="답글" style="display:inline" class="replyicon">
      <span class="comment3">삭제된 답글입니다.</span>
     </p>
     <hr>
     </div>

이렇게 반복문으로 children set 에서 하나하나 reply 라는 객체로 꺼내겠다~ 이런 의미를 갖도록 입력해주면 된다.
마찬가지로 삭제되었는지 아닌지를 확인하고, 삭제된 답글이라면 단순히 삭제를 시키지 말고 기록을 남길 수 있게 처리해주면 좋을거같아 추가해주었다.

그리고 기존의 부모댓글은 부모댓글일시에 자식댓글의 위에 출력되도록 해야하므로 조건문을 붙여준다.

<div th:each="articleComment : ${articleComments}">
   <div th:if="${articleComment.deleted != 'Y' && articleComment.isParent.equals('Y')}">
   ...

이렇게 자바에서 조건문 작성할때와 크게 다를것없이 반복문 밑에 추가해주면 된다.

여기서 빠진게 있다. 단순히 기존의 댓글 출력 부분을 갖다 붙여서 대댓글을 출력하게 했다면,

우리는 단순히 무한 대댓글을 구현한게 아닌 한 댓글에 여러개의 대댓글이 달릴 수 있게 구현했기 때문에,

대댓글 부분엔 답글달기 버튼이 없어야하고, 댓글 수정, 삭제도 동일하게 존재해야 한다.

       <a sec:authorize="isAuthenticated()" class="delete"
       th:if="${reply.userAccountDto.userId != null && reply.userAccountDto.userId == #authentication.getName()|| #authorization.expression('hasRole(''ADMIN'')')}"
      href="javascript:void(0);"
       th:data-uri="@{|/articles/comments/delete/${articleId}/${reply.id}|}">
     <button class="btn btn-light">삭제</button>
         </a>
     <button type="button"
      data-bs-toggle="collapse"
      sec:authorize="isAuthenticated()"
      th:attr="data-bs-target=|#r${reply.id}"
      th:if="${reply.userAccountDto.userId != null and reply.userAccountDto.userId.equals(#authentication.getName())}"
     style="display:inline" class="btn btn-light">수정
      </button>
       <div class="collapse" th:id="r + ${reply.id}">
       <form sec:authorize="isAuthenticated()"
       th:if="${reply.userAccountDto.userId != null and reply.userAccountDto.userId.equals(#authentication.getName()) || #authorization.expression('hasRole(''ADMIN'')')}"
 th:action="@{|/articles/comments/put/${article.id}/${reply.id}|}"
    th:object="${dto}" method="post">
    <div class="input-group" style="width:auto">
    <label for="content" class="form-label mt-4" hidden>댓글 작성</label>
    <input type="text" th:field="*{content}" class="form-control" id="replyUpdate" name="update"
    th:attr="placeholder=${reply.content}">
    <div class="field-error" th:errors="*{content}">
          오류2
    </div>
    <button href="javascript:void(0);"
     th:data-uri="@{|/articles/comments/update/${articleId}/${reply.id}|}"
    type="submit" class="update btn btn-outline-dark">수정
         </button>
      <br>
     </div>
     <br>
     </form>
    </div>

많이 복잡하지만 이런식으로 뷰를 구성해주면 된다. 콜랩스를 이용해서 기존의 수정 로직과 똑같이 이번엔 부모댓글의 첫 시작인 'c'가 아닌 'r'로 식별을 해서 수정 댓글 입력창이 등장하도록 한것이다.

마무리

이렇게 출력하게 된다.

대댓글을 이렇게 구현하는게 맞는지는 확신이 들지 않지만 내가 할 수 있는 선에서 최대한 복잡한 부분 없이 뷰에서 구현을 할려고 노력했다.

의견이나 질문은 댓글 남겨주세요 감사합니다~

profile
자스코드훔쳐보는변태

0개의 댓글