[TIL] 영화 검색 사이트 만들기 (4)

·2023년 10월 27일
1

TIL

목록 보기
16/85
post-thumbnail

오늘 한 일

  • 각 리뷰에 있는 모달 버튼 클릭 시 모달 창 생성
  • 모달창에 있는 닫기 버튼 클릭 시 모달 창 닫히기
  • 모달창에 있는 삭제 버튼 클릭 시 input 태그에 입력한 비밀번호 값과 해당 리뷰의 비밀번호 값과 비교 후 일치하면 삭제, 일치하지 않으면 alert 창 띄우기
  • 한마디로 리뷰 삭제 기능 구현 완.

오늘은 어제에 이어서 리뷰 삭제 기능을 마무리했다.
(여전히 CSS 는 끔찍 그상태)

모달창 생성하는 건 지난 mini project에서 해 봐서 어렵지 않았다. 않을 줄 알았다. 그랬는데... 그땐 꼴랑 세개여서 그냥 세 개를 일일이 만들었고 지금은 생성되는 리뷰마다 각각의 모달창을 동적으로 생성해야하고, 각각 이벤트리스너도 달아야해서 생각보다 쉽지는 않았다. (모달창 안에 있는 input태그, 삭제, 닫기 버튼 또한..)

1. 모달창 만들기

리뷰를 제출할 때 paintReview 함수가 실행되면서 리뷰가 생성되는데, 원래 ul 태그에 append 할 li 태그를 생성하기 위해 innerHTML을 사용하여 HTML 코드를 그대로 넣었는데, 뭔가 생성하면서 버튼마다 이벤트리스너도 달고 싶고 해서 결국 createElemet 를 사용하여 자바스크립트 코드로 다 변경하고 appendChild로 붙여서 생성하는 방식을 선택했다. 근데 꽤 긴 라인의 HTML코드를 js로 생성하려니까 코드가 굉장히 길어졌다..ㅋㅋ

마주한 에러

이전 버전

function paintReview(reviewObj) {
  const li = document.createElement("li");
  const openBtn = document.createElement("button");
  // 생략
  openBtn.addEventListener("click", openModal);
  closeBtn.addEventListener("click", closeModal);
  // 생략
  const div1 = document.createElement("div");
  div1.classList.add("hidden");
  div1.classList.add("modal__container");
  // 생략
  li.appendChild(openBtn);
  li.appendChild(div1);
  reviewLists.append(li);
}

const modal = document.querySelector(".modal__container"); // querySelectorAll을 써보자

function openModal() {
  modal.classList.remove("hidden");
}

function closeModal() {
  modal.classList.add("hidden");
}

원래 paintReview 함수에서 리뷰를 생성하면서 이벤트리스너를 바로 달아주었는데 원하는대로 동작하지 않았다. 모달창 띄우고 input에 비밀번호 입력하고 삭제를 클릭해도 내가 선택한 리뷰가 아니라 첫번째 리뷰가 삭제되는 이슈가 발생했다.
그 이유는 paintReview 함수를 여러번 돌면서 모달도 여러 개 생기고 modal__container라는 클래스명을 갖는 요소도 당연히 여러개 생기는데 modal__container라는 클래스명을 갖는 요소를 querySelecter로 가져와서 해당하는 첫번째 요소만 반환했기 때문이다.😅
querySelecterAll로 다 가져와서 NodeLists를 반환하고 이를 다시 Array로 변환헀다. 그랬더니 연쇄적인 문제 발생.
(아까 세번째 리뷰에서 모달 버튼 클릭해도 열렸던건 세번째 리뷰에 대한 모달이 아니라 첫번째 리뷰에 대한 모달이었던 것이다. 닫기 버튼도.. 삭제버튼도.. 그래서 삭제했을 때 해당 리뷰의 비번이 아닌 첫번째 리뷰 비번을 입력하면 삭제가 되어버리는..대환장)

수정 버전

const modals = document.querySelectorAll(".modal__container");
let modalsArray = Array.from(modals);

function paintReview(reviewObj) {
  openBtn.addEventListener("click", (e) => {
      const modal = modalsArray.find(
        (modal) => modal.parentElement.id === e.target.parentElement.id
      );
      modal.classList.remove("hidden");
    });
  // 생략
  closeBtn.addEventListener("click", (e) => {
      const modal = modalsArray.find(
        (modal) =>
          modal.parentElement.id ===
          e.target.parentElement.parentElement.parentElement.parentElement.id // 수정 예정
      );
      modal.classList.add("hidden");
    });
  // 생략
  deleteBtn.addEventListener("click", deleteReview);
}

그래서 paintReview 함수에 들어갈 모달 openBtn, closeBtn 에 대한 이벤트 함수를 위와 같이 수정했다.

  • modal__container라는 클래스명을 갖는 요소를 querySelectorAll로 다 가져오고 이 NodeList를 Array로 변환하여 modalsArray에 저장.
  • openBtn을 클릭했을 때 여러개의 모달 요소 중에 해당하는 요소를 뽑아내기 위해 find 메서드를 사용하여 modalsArray를 돌면서 모달을 포함하고 있는 li 태그의 id 값들과 내가 클릭한 버튼을 포함하고 있는 li 태그의 id 값이 일치하는 녀석을 반환했다.
  • closeBtn도 마찬가지였는데 openBtn은 리뷰안에 있어서 parentElement 한번만 타고 올라가면 li 태그에 접근할 수 있었던 반면에 closeBtn(취소버튼)은 모달창 안에 있어서 parentElement를 총 4번이나 타고 올라가야 li 태그에 접근할 수 있었다.
  • 일단 기능 구현이 우선이지..! 하면서 냅다 4번을 썼는데 클린한 코드가 아닌 것 같다. 나중에 조원 분이 closest() 라는 메서드를 사용해보는게 어떻겠냐는 제안을 해주셨는데..!! 내가 찾던 놈이다.! 주말에 저거 활용해서 리팩토링 해보아야지.

2. review 삭제하기

모달창을 만들기 전에 삭제 기능을 먼저 구현 해 놓았다.

function deleteReview(event) {
  const li =
    event.target.parentElement.parentElement.parentElement.parentElement; // 수정 예정
  const targetReview = reviewStorage.find(
    (review) => review.id === parseInt(li.id)
  );
  const modalInputs = document.querySelectorAll(".modal__input");
  const modalInputsArray = Array.from(modalInputs);
  const input = modalInputsArray.find(
    (input) =>
      input.parentElement.parentElement.parentElement.id ===
      event.target.parentElement.parentElement.parentElement.parentElement.id // 수정 예정
  );
  if (targetReview.password !== input.value) {
    alert("비밀번호가 일치하지 않습니다.");
    input.value = "";
    return;
  }
  reviewStorage = reviewStorage.filter(
    (review) => review.id !== parseInt(li.id)
  );
  saveReview();
  li.remove();
}
  • 내가 클릭한 삭제 버튼의 li 태그를 찾는다.
  • 리뷰 data가 객체형태로 저장된 배열인 revieStorage를 순회하면서 review들 중 해당 리뷰의 li 태그 id 값과 review.id가 일치하는 첫번째 친구를 반환하여 targetReview로 저장했다.
  • 모달 창의 input 태그도... 마찬가지로 find 메서드를 활용하여 내가 클릭한 삭제 버튼의 input 태그를 찾았다.
  • 여기서도 parentElement 남발..ㅋㅋ 하.. 더 간결하게 수정하고 싶다.
  • 내가 찾은 targetReview의 password 와 input의 value 가 일치하지 않는 경우 alert 창을 띄우고 함수를 종료.
  • 그렇지 않으면 filter 메서드를 활용히야 reviewStorage에 리뷰의 id와 li 태그의 id 가 일치하지 않는 녀석들만 걸러내서 다시 담는다. (일치하는 녀석은 reviewStorage에서 걸러짐)
  • 그리고 saveReview 함수를 실행하여 reviewStorage를 localStorage에 다시 저장한다.
  • 마지막으로 li 태그를 remove 한다~!

3. 에러는 계속된다

모달창 띄우고, 비밀번호 입력하고 일치하지 않을 때 alert 창 띄우고 일치하면 삭제되는 것 까지 잘 되는 것을 확인했다~!!! 으아 끝났다! 라고 생각했던 것도 잠시...
이전에 생성해 둔 리뷰들을 삭제하는 것은 잘 되는데, 방금 막 생성한 리뷰의 모달버튼을 클릭하면 동작이 되지 않는 것을 발견했다. 😂 근데 이상하게 새로고침하고 다시 버튼을 클릭하면 정상적으로 동작하는..
방금 만든 리뷰를 삭제하려고 매번 새로고침 할 수는 없는 노릇..
이 문제가 해결이 잘 안되어서 결국 튜터님을 찾아갔고, 힌트를 얻어서 곧바로 수정했다.
문제는 modalsArraymodal__container라는 클래스명을 갖는 요소들이 담겨있고, 그걸 통해서 모달 오픈 버튼도 동작하는데
paintReview 함수를 실행할 때 생성된 modal은 modalsArray에 담겨있지 않아서 발생하는 문제였다.😇
paintReview 함수에 이 한줄을 추가하니 잘 동작이 되었다. 으아와아아

modalsArray.push(div1);

남은 과제

  • 모달 창 바깥부분을 클릭해도 닫히는 기능 구현
  • 모달 창 input에 입력 값 없이 삭제하려고 하면 "입력값이 없습니다" alert 띄우기
  • deleteReview, paintReview 함수 리팩토링. 간결하게 해보고 싶어😢
  • CSS 얼른 다듬자.. 못생긴 화면에서 그만 작업하고 싶어..
profile
느리더라도 조금씩, 꾸준히

0개의 댓글