HTML, CSS, JS만을 활용한 영화 검색 페이지 구현(2)

    반응형

    HTML, CSS, JS만을 활용해 영화 검색 페이지를 구현하면서 기본적인 구현 과제를 제외하고 추가적인 기능으로 무한 스크롤을 구현하려고 했다. 기존에 React를 공부했을 때 연습해 본 경험이 있어서 쉽게 구현할 거라 생각했지만 생각 외로 너무 고민했던 부분들이 많았기에 정리하고 복습하려고 한다.

     

    이벤트 등록


      if (currentPage === "movieList.html") {
        $searchResults.addEventListener("click", (e) => {
          bookMarkCheck(e);
        });
    
        $searchInput.addEventListener("input", async (e) => {
          inputSearch = e.target.value.toLowerCase().trim();
          if (inputSearch) {
            await handleSearch();
          }
        });
    
        $searchInput.addEventListener("keydown", async (e) => {
          if (e.key === "Enter") {
            if (!inputSearch) {
              alert("검색어를 입력해 주세요");
              return;
            }
            await handleSearch();
          }
        });
    
        $searchBtn.addEventListener("click", async () => {
          inputSearch = $searchInput.value.toLowerCase().trim();
          if (!inputSearch) {
            alert("검색어를 입력해 주세요");
            return;
          }
          await handleSearch();
        });
      }

     

    구현 조건이 실시간 검색 및 엔터키, 클릭 버튼 모두 구현이라 각각의 이벤트 리스너를 통해 이벤트를 등록했다. 이벤트를 등록하고 이벤트 조건이 맞을 때마다 함수가 실행되도록 구현했다.

     

    const handleSearch = async () => {
      page = 1;
      hasNextPage = true;
      loading = true;
    
      const { totalPages: searchPages, totalResults: searchResults } =
        await displaySearchResultCards(page, inputSearch, $searchResults);
    
      totalPages = searchPages;
      totalResults = searchResults;
    
      if (totalPages >= 1) {
        initializeSearchObserver();
      }
    };

     

    사용자들이 검색을 하고 지우고 다시 다른 값을 검색할 수 있기 때문에 무한 스크롤에서 사용한 상태들을 초기화해주고 데이터를 가져와 UI를 그려주는 함수를 사용했다. 그리고 넘어오는 데이터의 페이지가 한 페이지 이상일 때만 무한 스크롤을 위한 옵저버를 잡아주는 함수를 추가로 동작하도록 했다.

     

    검색 UI를 그려주는 함수

    // 영화 검색결과 UI 함수
    export const displaySearchResultCards = async (page, title, element) => {
      const moviesData = await getSearchMovies(page, title);
    
      page === 1 ? (element.innerHTML = "") : null;
    
      if (moviesData && moviesData.results.length > 0) {
        try {
          moviesData.results.forEach((movie) => {
            const bookMarks = JSON.parse(localStorage.getItem("movies")) || [];
            const isBookMarked = bookMarks.some(
              (bookmark) => bookmark.id === String(movie.id)
            );
    
            const divEl = document.createElement("div");
            divEl.setAttribute("key", movie.id);
            divEl.classList.add("movie__card");
            const movieItem = `
            <a class='movie__link' href='./movieDetail.html?id=${movie.id}'>
              <img class="movie__mark" src='./images/${
                isBookMarked ? "fillstar.svg" : "star.svg"
              }' /> 
              <img class="movie__img" src=${
                movie.backdrop_path
                  ? `https://image.tmdb.org/t/p/w300${movie.backdrop_path}`
                  : "https://ekari.jp/wp-content/uploads/2020/12/noimage.png"
              } alt="${movie.title}"/>
              <div class="movie__text__container">
                <h3 class="movie__title">${strCut(movie.title, 20)}</h3>
                <p class="movie__date"> 개봉 : ${movie.release_date}</p>
                <p class="movie__desc">${
                  movie.overview ? strCut(movie.overview, 35) : "설명이 없습니다."
                }</p>
              </div>
            </a>
            `;
    
            divEl.innerHTML = movieItem;
            element.append(divEl);
          });
          console.log("moviesData", moviesData);
          return {
            totalPages: moviesData.total_pages ? moviesData.total_pages : 0,
            totalResults: moviesData.total_results ? moviesData.total_results : 0,
          };
        } catch (error) {
          throw new Error(`이런 ${error} 가 발생했습니다.`);
        }
      } else {
        element.innerHTML = `<p class="movie__no__result">${title} 에 대한 검색 결과가 없습니다.</p>`;
      }
    };

     

    search movies

     

    처음 검색을 하게 되면 아래와 같이 데이터가 출력되게 UI를 그려주면서 총 페이지 값과  결과 값을 객체의 형태로 리턴해주도록 했는데 검색 결과는 잘 출력되지만 자꾸 totalPages 값을 찾을 수 없다는 에러가 발생했다.

     

    error message

     

    코드를 몇 번이고 다시 읽어봐도 코드의 동작 순서에 오류가 없는 것 같아 결국 튜터님께 문의를 요청했고 코드를 정상적으로 동작 시킬 수 있었다.

     

    // 영화 검색 페이지 그리기
    export const displaySearchResultCards = async (page, title, element) => {
      const moviesData = await getSearchMovies(page, title);
    
      const returnData = {
        totalPages: moviesData.total_pages ? moviesData.total_pages : 0,
        totalResults: moviesData.total_results ? moviesData.total_results : 0,
      };
    
      page === 1 ? (element.innerHTML = "") : null;
    
      if (moviesData && moviesData.results.length > 0) {
        try {
          moviesData.results.forEach((movie) => {
            const bookMarks = JSON.parse(localStorage.getItem("movies")) || [];
            const isBookMarked = bookMarks.some(
              (bookmark) => bookmark.id === String(movie.id)
            );
    
            const divEl = document.createElement("div");
            divEl.setAttribute("key", movie.id);
            divEl.classList.add("movie__card");
            const movieItem = `
            <a class='movie__link' href='./movieDetail.html?id=${movie.id}'>
              <img class="movie__mark" src='./images/${
                isBookMarked ? "fillstar.svg" : "star.svg"
              }' />
              <img class="movie__img" src=${
                movie.backdrop_path
                  ? `https://image.tmdb.org/t/p/w300${movie.backdrop_path}`
                  : "https://ekari.jp/wp-content/uploads/2020/12/noimage.png"
              } alt="${movie.title}"/>
              <div class="movie__text__container">
                <h3 class="movie__title">${strCut(movie.title, 20)}</h3>
                <p class="movie__date"> 개봉 : ${movie.release_date}</p>
                <p class="movie__desc">${
                  movie.overview ? strCut(movie.overview, 35) : "설명이 없습니다."
                }</p>
              </div>
            </a>
            `;
    
            divEl.innerHTML = movieItem;
            element.append(divEl);
          });
          console.log("moviesData", moviesData);
        } catch (error) {
          throw new Error(`이런 ${error} 가 발생했습니다.`);
        }
      } else {
        element.innerHTML = `<p class="movie__no__result">${title} 에 대한 검색 결과가 없습니다.</p>`;
      }
    
      return returnData;
    };

     

    수정된 코드는 위와 같은데 우선 데이터를 가져오면 앞에서 페이지와 결과값이 없을 때를 대비한 가드 역할을 해줄 수 있도록 리턴할 객체를 새로 만들어주었다. 기존에 if 문안에서 리턴되던 값도 가드 역할을 해주면서 함수 마지막에 리턴될 수 있도록 최종적으로 빼주었더니 정상적으로 오류 없이 동작하게 되었다.

     

    원인

     

    IntersectionObserver API를 사용해서 계속해서 페이지를 변경하면서 UI를 그리는 함수를 호출하는데 호출하면서 영화 제목에 없는 영화 데이터를 가져올 때는 총 페이지나 결과 값들이 모두 undefined였고, 콘솔로 확인해 본 결과 try 문에서 리턴되던 데이터들이 제대로 전달되지 않았다.

     

    지금은 이 정도가 원인이라고 생각되는데 좀 더 정확한 원인을 코드를 읽어보면 생각해 봐야겠다.

    반응형

    댓글