내일배움캠프 React_7기 TIL - 14.[JavaScript 개인과제] 영화 검색 사이트 만들기 (3)

·2024년 10월 23일
0

외로운 개인 프로젝트의 길.

마지막 메인 기능 - 북마크 구현 완료

마지막 메인 기능이자 요구사항이였던 북마크 기능을 구현했다.

모달 창에 북마크하기 버튼을 누르면 localStorage에 저장하고, 내 북마크 페이지에서 카드로 띄워주고자 했다.
(참고로, 북마크하기 대신 아이콘을 넣을 예정이지만 일단 급해서 먼저 넣음)

신경 써야 했던 점
1. 북마크 카드에도 페이지네이션을 적용할 것(이전 tmdb API에서 가져왔던 것들은 알아서 1page당 20개씩의 정보를 줬는데, 북마크는 페이지당 20개의 카드만 띄워주도록 처리 해야 했음)
2. 이미 북마크가 된 영화였다면, 북마크에서 삭제되도록 구현

또한 고민되었던 점

1.무비 디테일 값을 로컬 스토리지에 다 담아넣기
2.무비 아이디값만 로컬 스토리지에 넣고, fetchMovieDetail 실행

둘 중 뭐가 더 효율적일까 였다. 1번은 로컬 스토리지 용량도 걱정되고 데이터 최신성을 유지하기 어렵다고 판단되어 2번으로 진행함. API 호출 제한 이런건 안 걸리겠지...?

로컬 스토리지에 북마크한 영화 저장

function bookmarkingMovies(movieId) {
  let bookmarkMovies = JSON.parse(localStorage.getItem("bookmarkMovies")) || [];

  // 이미 있는 ID면 삭제하고, 없으면 추가
  if (bookmarkMovies.includes(movieId)) {
    bookmarkMovies = bookmarkMovies.filter((movie) => movie !== movieId);
  } else {
    bookmarkMovies.unshift(movieId);
  }

  localStorage.setItem("bookmarkMovies", JSON.stringify(bookmarkMovies));
}

영화의 id값을 받아 북마크를 추가하는 함수이다, localStorage에서 getItem을 해오고 없다면 빈배열을 추가하는 것은 검색 기록 함수와 방식을 같게 했다.

만약 이미 있는 id면 삭제하도록 했고(해당 id값 없이 담기도록 할당하는 방식), 없다면 배열의 맨 앞에 추가하도록 구현했다.

모달창 내 북마크 버튼 만들기

const bookmarkButton = document.createElement("div");
  bookmarkButton.className = "bookmark-button";
  if (getBookmarkingMovies().includes(movieDetails.id)) {
    bookmarkButton.innerHTML = `<div>북마크해제</div>`;
  } else {
    bookmarkButton.innerHTML = `<div>북마크하기</div>`;
  }
  bookmarkButton.onclick = () => {
    // 북마크 추가/제거 함수 호출
    bookmarkingMovies(movieDetails.id);

    // 버튼 텍스트 업데이트
    if (getBookmarkingMovies().includes(movieDetails.id)) {
      bookmarkButton.innerHTML = `<div>북마크해제</div>`;
    } else {
      bookmarkButton.innerHTML = `<div>북마크하기</div>`;
    }
  };

getBookmarkingMovies는 로컬스토리지에 저장된 id값을 가져오는 함수. 현재 모달로 뜬 영화의 id값이 있다면 "북마크해제"가, 없다면 "북마크하기" 가 보여지도록 했다.
그리고 북마크 버튼을 누르면 bookmarkingMovies(movieDetails.id);가 실행되도록 했는데, 로컬 스토리지에 저장되어도 UI가 업데이트가 되질 않아 버튼을 누를 시 한번 더 innerHTML을 조작했다. (크아악 바닐라)

북마크한 영화 카드 띄우기

createMovieCardpaginationFnc을 재활용하여 동일한 UI로 뿌리고자 했다.
하지만 첫번째 난관. 영화 데이터를 가져오고 카드를 생성하는 loadMovies 가 이미 너무 복잡했다... ^^

매개변수를 하나 더 줘서 구분할 순 없어서 그냥 query에 "bookmarked"가 들어오면 북마크한 영화 띄우는 함수를 실행하도록 했다.

// 영화 데이터를 가져오고 카드 생성
const loadMovies = async (fetchFunction, query) => {
  let data;
  if (query === "bookmarked") {
    await fetchFunction();
    return;
  }
  if (query) {
    // 제목별 검색의 경우
    data = await fetchFunction(query, currentPage);
    createMovieCard(filteredMovies);
    endPage = filterdTotalPages;
  } else {
    // 전체 영화 데이터 로드의 경우
    data = await fetchFunction(currentPage);
    createMovieCard(movies);
    endPage = totalPages;
  }

  if (!data) {
    return;
  }

  if (data.length === 0) {
    document.getElementById(
      "movies-container"
    ).innerHTML = `<div>검색 결과가 없습니다.</div>`;
  }
  window.scrollTo({ top: 0, behavior: "smooth" }); // 부드러운 스크롤
  paginationFnc(endPage);
};

...

// 초기 영화 데이터 로드
if (window.location.pathname.split("/").pop() === "index.html") {
  loadMovies(fetchMovies);
} else if (window.location.pathname.split("/").pop() === "search.html") {
  console.log(searchQuery);
  loadMovies(fetchMovieByTitle, searchQuery);
} else if (window.location.pathname.split("/").pop() === "myBookmark.html") {
  loadMovies(loadBookmarkedMovies, "bookmakred");
}

북마크 기능을 위해 loadMovies를 불러오면 data가 할당되어있지 않아 error가 발생했는데, if (!data) return; 으로 넘겼다.(주먹구구)

const moviesPerPage = 20; // 페이지당 영화 수

const loadBookmarkedMovies = async () => {
  const bookmarkedMovieIds = getBookmarkingMovies();

  if (bookmarkedMovieIds.length === 0) {
    document.getElementById(
      "movies-container"
    ).innerHTML = `<div>북마크한 영화가 없습니다.</div>`;
    return;
  }

  // 전체 영화 ID에 대해 fetchMovieDetail 호출
  const bookmarkMovies = await Promise.all(
    bookmarkedMovieIds.map((id) => fetchMovieDetail(id))
  );

  const totalBookmarkMovies = bookmarkMovies.length;
  endPage = Math.ceil(totalBookmarkMovies / moviesPerPage); // 전체 페이지 수 계산

  // 현재 페이지에 해당하는 영화만 표시
  const startIndex = (currentPage - 1) * moviesPerPage;
  const moviesToDisplay = bookmarkMovies.slice(
    startIndex,
    startIndex + moviesPerPage
  );

  // 데이터를 기반으로 영화 카드를 생성
  if (moviesToDisplay.length > 0) {
    createMovieCard(moviesToDisplay); // movieDetails는 영화 디테일 배열
  } else {
    console.log("영화 가져오기 실패");
  }
  window.scrollTo({ top: 0, behavior: "smooth" }); // 부드러운 스크롤
  paginationFnc(endPage); // 페이지네이션 함수 호출
};

loadBookmarkedMovies 함수를 구현하여 북마크된 영화를 카드로 띄우고, 페이지네이션까지 적용하였다. await Promise.all로 로컬스토리지에 있던 id값들에 대해 다 fetchMovieDetail를 해줬다.
북마크된 영화들의 개수(length)를 사용해서 endPage를 계산해줬다.

  • Math.ceil : 올림한 정수 반환

currentPage는 현재 보고 있는 페이지 번호인데(pageantionFunc에서 구현), 여기에 *20(moviesPerPage)을 해서 페이지당 시작할 인덱스를 구한다. 그러면 1페이지에선 0번째 인덱스부터, 2페이지에선 20번째 인덱스에 있는 영화부터 보여줄 수 있다.
moviesToDisplayslice한 페이지당 영화 목록을 불러오고, 영화 카드를 생성한다.

추가 수정! - 이전, 다음 버튼

페이지 버튼 양 옆의 <<, >> 버튼은 10page씩 페이지 버튼을 넘기는 버튼인데, 전체 페이지가 10을 넘지 않았을 경우를 막아놓지 않았다! 그래서 간단히 수정.

// 다음 페이지 버튼 클릭 시
next_button.addEventListener("click", () => {
  if (startPage + 10 <= endPage) {
    startPage += 10;
    paginationFnc(endPage);
  }
});

// 이전 페이지 버튼 클릭 시
prev_button.addEventListener("click", () => {
  if (startPage > 1) {
    startPage -= 10;
    paginationFnc(endPage);
  }
});


북마크된 영화가 잘 뜨는 모습이다~.

이로써 메인 기능은 완료!

이제 css남았는데
css가 제일 .... 제일 .....
미루고싶다.

하지만 이제는 더 이상?

자 ~ 힘내자.

profile
내배캠 React_7기 이수중

1개의 댓글

comment-user-thumbnail
2024년 10월 23일

멋지따 sun-!

답글 달기

관련 채용 정보