메가바이트 스쿨 5주차 (1/13) Coding Test, 영화 API 과제 제출

정영찬·2023년 1월 13일
0
post-thumbnail

Coding Test

프로그래머스 코딩 테스트 lv.0 문제풀이 완료
저주의 숫자 3 문제 : https://school.programmers.co.kr/learn/courses/30/lessons/120871

function solution(n) {
    let answer = 0
    let index = 1
    
    while (index <= n){
        index++
        answer++
        
        while(String(answer).includes("3") || answer % 3 === 0){
            answer++
        }
    }
    
    
  return answer
}

3x 마을 사람들은 3을 저주의 숫자라고 생각하기 때문에 3의 배수와 숫자 3을 사용하지 않습니다.
숫자를 세는데 3의 배수거나 3이 들어있으면 셀수 없다고 한다 (세상에...)

아무튼 사용자가 숫자를 입력하면 그게 3x마을사람들이 읽는 숫자로 변경되는 함수를 작성하는건데.

지금 보면 엄청 간단한데 여기까지 오는데 생각을 너무 복잡하게 한것 같다. while을 사용해서 하는 것이니만큼 항상 infinite loop가 발생하지 않게 조심하자.

영화 API 과제 제출

github 링크: https://github.com/jyc-coder/KDT4-M2/tree/JeongYeongChan

netlify 링크 : https://app.netlify.com/sites/astounding-medovik-dc609f/deploys/63c16358fe79490355843462

🎬 영화 검색

  • 과제 기한:
    • 과제 수행 기간: 2023년 01월 02일(월) ~ 2023년 01월 13일(금)
    • 서로 리뷰 기간: 2023년 01월 16일(월) ~ 2023년 01월 20일(금)
  • 내용:
    • 주어진 API를 활용해 '완성 예시' 처럼 자유롭게 영화 검색 기능을 구현해보세요!

요구사항

필수 요구사항은 꼭 달성해야 하는 목표로, 수정/삭제는 불가하고 추가는 가능합니다.
선택 요구사항은 단순 예시로, 자유롭게 추가/수정/삭제해서 구현해보세요.
각 요구사항은 달성 후 마크다운에서 - [x]로 표시하세요.

❗ 필수

  • 영화 제목으로 검색 가능하고 검색된 결과의 영화 목록이 출력돼야 합니다.
  • jQuery, React, Vue 등 JS 라이브러리와 프레임워크는 사용하지 않아야 합니다.
  • 스타일(CSS) 라이브러리나 프레임워크 사용은 자유입니다.

❔ 선택

  • 한 번의 검색으로 영화 목록이 20개 이상 검색되도록 만들어보세요.
  • 영화 개봉연도로 검색할 수 있도록 만들어보세요.
  • 영화 목록을 검색하는 동안 로딩 애니메이션이 보이도록 만들어보세요.
  • 무한 스크롤 기능을 추가해서 추가 영화 목록을 볼 수 있도록 만들어보세요.
  • 영화 포스터가 없을 경우 대체 이미지를 출력하도록 만들어보세요.
  • 단일 영화의 상세정보(제목, 개봉연도, 평점, 장르, 감독, 배우, 줄거리, 포스터 등)를 볼 수 있도록 만들어보세요.
  • 영화 상세정보가 출력되기 전에 로딩 애니메이션이 보이도록 만들어보세요.
  • 영화 상세정보 포스터를 고해상도로 출력해보세요.(실시간 이미지 리사이징)
  • 차별화가 가능하도록 프로젝트를 최대한 예쁘게 만들어보세요.
  • 영화와 관련된 기타 기능도 고려해보세요.

구현 페이지

ezgif com-gif-maker (53)

기능

영화 검색

검색창에 텍스트를 입력하고 우측에 위치한 검색 버튼, 혹은 키보드로 enter를 누르면 검색 결과를 아래에 보여줍니다.

// 엔터 입력시 검색 시작
inputEl.addEventListener('keydown', function (e) {
  if (e.key === 'Enter' && !e.isComposing) {
    getMovies(true)
  }
})
// 버튼 클릭시 검색 시작
buttonEl.addEventListener('click', function () {
  getMovies(true)
})
// 10개만 생성된 데이터
let movies = []
// 렌더링된 모든 영화의 데이터
const totalMovies = []
// 생성된 모든 li 엘리먼트를 담은 배열
const totalMovieEls = []
// api에 데이터 가져오기, 렌더링
async function getMovies(first) {
  // 재검색을 실시해도 1페이지부터 검색될수 있도록 변경
  if (first) {
    movieListEl.innerHTML = ''
    pageNum = 1
  }
  const res = await fetch(
    `https://omdbapi.com/?apikey=7035c60c&s=${searchText}&page=${pageNum}`
  )

  pageNum++
  const json = await res.json()
  movies = json.Search
  // 가져온 데이터를 totalMovies에 저장
  totalMovies.push(...movies)

  // 총 영화 편수
  const totalResult = parseInt(json.totalResults)
  // 렌더링 될수 있는 페이지의 수
  const pageLimit = Math.round(totalResult / 10)
  // 렌더링 엘리먼트
  const movieEls = movies.map(function (movie) {
    const liEl = document.createElement('li')
    const titleEl = document.createElement('div')
    const titleNameEl = document.createElement('h2')
    const posterEl = document.createElement('img')
    // 생성되는 엘리먼트의 class 이름 지정
    liEl.className = 'result__list__item'
    titleEl.className = 'result__list__item__title'
    titleNameEl.className = 'result__list__item__title__name'
    posterEl.className = 'result__list__item__poster'
    titleNameEl.textContent = movie.Title
    // div 안에 h2 추가
    titleEl.append(titleNameEl)
    posterEl.src =
      movie.Poster.indexOf('N/A') === -1
        ? movie.Poster
        : 'https://dummyimage.com/300x445/000/3039b0.jpg&text=not+found'

    liEl.append(titleEl, posterEl)

    return liEl
  })

  movieListEl.append(...movieEls)
  totalMovieEls.push(...movieEls)

무한 스크롤

검색 결과 li 엘리먼트 중에서 가장 마지막을 InterSectionObserver를 사용해서 기준점으로 잡은 다음, 사용자가 스크롤을 내렸을 때 엘리먼트가 보이는 순간 getMovies(false)메서드를 호출해서 추가로 렌더링하여 결과를 보여줍니다.

/// 무한 스크롤
const lastItem = document.querySelector('.result__list__item:last-child')
const lastItemObserver = new IntersectionObserver((entries) => {
  const listItem = entries[0]
  if (!listItem.isIntersecting) return
  // 조건 해결 pageNum이 최대 페이지수를 넘지 않는 선에서 렌더링을 시도하게 수정함
  if (pageLimit >= pageNum) {
    getMovies(false)
  }
  // undefined 예외처리
  if (listItem.typeOf !== undefined) {
    lastItemObserver.unobserve(lastItem.target)
  }
})

lastItemObserver.observe(lastItem)

Back To Top 버튼

검색창 엘리먼트 .searchInterSectionObserver를 사용해서 기준점으로 잡은 다음 해당 엘리먼트가 보이지 않게되면 바로 버튼이 보이게 되며 클릭하면 scrollToTop메서드가 호출되어 화면 맨위로 이동하게 됩니다.

/// 스크롤 버튼 기능 구현 ///
const flexBoxEl = document.querySelector('.flex-box')
const topBtnEl = document.querySelector('.topbtn')
const targetEl = document.querySelector('.search')
const rootElement = document.documentElement
// 검색 결과를 둘러보기위해 스크롤 해서 검색 엘리먼트가 보이지 않는 순간
// 하단에 맨 위로 이동하게 해주는 버튼이 나타남
function moveTop(entries, observer) {
  entries.forEach((entry) => {
    if (entry.isIntersecting) {
      flexBoxEl.style.opacity = '0'
    } else {
      flexBoxEl.style.opacity = '1'
    }
  })
}
// 검색창 엘리먼트를 감시하는 IntersectionObserver 생성
const topObserver = new IntersectionObserver(moveTop)
topObserver.observe(targetEl)

// 버튼을 클릭하게 되면 맨위로 스크롤 된다.
function scrollToTop() {
  // 맨 위로 스크롤
  rootElement.scrollTo({
    top: 0,
    behavior: 'smooth',
  })
}

// 버튼 엘리먼트에 이벤트 추가
topBtnEl.addEventListener('click', scrollToTop)

상세 페이지 기능

사용자가 검색결과중 하나를 클릭하게 되면 모달 엘리먼트에 영화 검색 api로 가져온 데이터와 영화 상세 검색API를 통해 가져온 줄거리 데이터가 추가되어 렌더링됩니다.
Close 버튼을 클릭하면 .modaldisplay 속성을 none으로 변경하여 원래의 화면을 돌아옵니다.

/// 모달 팝업 기능
// 렌더링된 리스트 아이템을 클릭하면 모달창이 나타나게 설정
const modalEl = document.querySelector('.modal')
const modalClose = document.querySelector('.modal__close-btn')

totalMovieEls.forEach(function (liEl, idx) {
  liEl.addEventListener('click', async () => {
    modalEl.style.display = 'flex'
    const modal = document.querySelector('.modal')
    const modalMain = document.querySelector('.modal__main')
    const modalTitle = document.querySelector('.modal__main__title')
    const modalYear = document.querySelector('.modal__main__year')
    const modalPoster = document.querySelector('.modal__main__poster')
    const modalPlot = document.querySelector('.modal__main__plot')
    // 클릭하는 순간 전체 데이터 제거
    modal.innerHTML = ''
    // 이전 api fetch에서 사용할수 잇는 데이터는 다시 사용
    const movieId = totalMovies[idx].imdbID
    const mvTitle = totalMovies[idx].Title
    const mvYear = totalMovies[idx].Year
    const mvPoster =
      totalMovies[idx].Poster.indexOf('N/A') === -1
        ? totalMovies[idx].Poster
        : 'https://dummyimage.com/300x445/000/3039b0.jpg&text=not+found'
    // 상세 정보 api 호출
    const detailRes = await fetch(
      `https://omdbapi.com/?apikey=7035c60c&i=${movieId}&plot=full`
    )
    // 데이터 가공
    const detailJson = await detailRes.json()
    const mvPlot =
      detailJson.Plot.indexOf('N/A') === -1
        ? detailJson.Plot
        : 'There is no Plot'

    modalTitle.textContent = mvTitle
    modalYear.textContent = mvYear
    modalPlot.textContent = mvPlot
    modalPoster.src = mvPoster
    // 엘리먼트에 데이터 추가
    modal.append(modalMain)
  })
})
modalClose.addEventListener('click', function () {
  modalEl.style.display = 'none'
})

기능 구현을 하고 다 됐다 싶으면 오류가 생기고, 오류를 풀었다 싶으면 원하는 대로 동작하지 않아서 다시 코드를 수정하고, 수정했다 싶으면 오류가 생기고.... (살려줘)

문제 : 상세 페이지가 원하는 내용을 렌더링 하지 않음

상세페이지를 제일 마지막에 구현했는데, 인덱스 값을 movieEls 배열을 기준으로 정했기 때문에 인덱스가 계속 초기화되는 현상 때문에 검색 결과 후반부의 상세페이지가 맞지 않았던 것이다.

데이터를 10개만 가져오는데 메서드를 계속 호출하면 10개씩만 가져오니까 인덱스도 당연히 10개까지만 지정되었다.

해결 방법

렌더링 메소드를 호출할 때마다 api로부터 받아오는 데이터를 계속 저장하는 빈 배열을 따로 생성했다. totalMovieEls을 forEach로 동작하니 해결되었다!

이렇게 문제를 만나고 수정하고 해결하는 무한 과정을 계속 되풀이 하다보면 어느새 과제가 끝나있었다. 물론 선택사항을 많이 구현하지 못했지만 나중에라도 한번 구현해보는 시간을 가져야겠다.

profile
개발자 꿈나무

0개의 댓글