영화 검색 사이트를 만드는 자바스크립트 바닐라코딩
이전까지 한 작업 (아래클릭)
이제, 메인 기능인 검색기능 (TMDB API 데이터) 구현 그리고 자잘한 기능들을 넣을 예정이다.(홈화면으로가기, 마우스이벤트)
아래 코드 주석에 내가 이해한 바를 모두 적어놓았다.
1. 검색기능
코드보기
더보기
const searchArea = document.querySelector('.search_area');
const IMAGE_BASE_URL = "https://image.tmdb.org/t/p/w500/";
const movieCard = document.querySelector(".movieCard");
const movieImage = document.querySelector(".movieImage");
const searchInput = document.getElementById("search_input")
.addEventListener("keyup", function(e) {
if (e.code === 'Enter') {
document.getElementById("submit").click();
}
});
// --------------TMDB API: API request and get ----------------- //
const options = {
method: 'GET',
headers: {
accept: 'application/json',
Authorization: 'Bearer eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiIzZjI5M2EzNGQ5YTdhYmE3ZmE2YjA1MmVkNDBhNzAzNSIsInN1YiI6IjY1MmY3YTM2MDI0ZWM4MDEwMTU0MzQ3NCIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ.4UlNrO-aIaz-15g60eboaP-Zr-ck8iYXpu1qfhKZm0E'
}
};
const API_url = "https://api.themoviedb.org/3/movie/top_rated?language=en-US&page=1"; // url이 길기때문에 변수안에 넣어줌
function fetchSecondThen(data) {
// ---------- 초기 세팅 ----------- //
let dataIndex = data['results']; // 우리가 앞으로 쓸 영화데이터 = 데이터(data)의 안에 있는 results 값 :result는 [ { "keyname" : "value", "keyname" : [ 배열 value ] } ] 형태로 묶여있다.
// ----------- 영화데이터 보여주기 ----------- //
const cardList = document.querySelector('.list_area'); // 받아온 데이터를 넣어줄 공간
cardList.innerHTML = ""; // 받아온 데이터를 넣어줄 공간 안에 html 내용 초기화
dataIndex.forEach((i) => { // i 는 index. 영화데이터 묶음에 있는 모든 요소를 돈다.
let myTitle = i['title']; // i번째 인덱스, 객체안에 요소 'title'
let myOverView = i['overview'];
let myPosterPath = i['poster_path'];
let myVoteAverage = i['vote_average'];
let myId = i['id'];
// html에 넣어지는 tag + 콘텐츠
let temp_html = `
<div data-id="${myId}" class="movieCard" >
<img class="movieImage" src="${IMAGE_BASE_URL}${myPosterPath}" alt="">
<div class="movieName">
<h3>${myTitle}</h3>
<p class="movieGrade">Rating : ${myVoteAverage}</p>
</div>
<p class="movieExplanation">${myOverView}</p>
</div>`;
cardList.insertAdjacentHTML('beforeend', temp_html); // temp_html을 데이터 넣어줄 공간안에서 마지막 자식 이후에 위치시키기
});
// ------------- 영화 카드 클릭시 ID 보여주기 ----------- //
const movieIdShowbyClick = function () {
const movieCards = document.querySelectorAll(".movieCard"); // 영화카드 모두 찾아 선택
movieCards.forEach(card => { // 영화카드들의 배열을 한번씩 모두 함수에 대입
card.addEventListener("click", function () { // "이벤트명", 함수 이하의 동작을 하는 이벤트 등록
let movieCardId = this.getAttribute("data-id"); // 영화카드클래스 안에 속성 data-id 값
alert(`영화 ID : ${movieCardId}`); // 영화 data id 값 alert
})
})
}
movieIdShowbyClick(); // 실행
// -------- 영화 검색 ----------- //
function movieSearch(event) { // 영화검색기능
event.preventDefault(); // submit 되는 동시에 창 새로고침을 방지
const searchInput = document.getElementById('search_input'); // 검색엔진박스
const query = searchInput.value.toLowerCase(); // 검색엔진박스 내 value 값을 소문자로 변환
searchInput.value = ""; // 검색엔진박스 기본 value값은 빈문자열
console.log(query);
if (query.trim() === '') { // a문자열 양 끝의 공백을 제거 -> 소문자로변환된 value값이 빈문자열이면
alert('검색어를 입력해주세요'); // 아래 alert 뜨게 해라
return; // 리턴은 함수를 즉시 빠져 나오는 기능
}
// API 데이터를 배열로 가져오기
let title_list = dataIndex.map((item) => { // 영화데이터 배열을 그대로 복사해 새로운 배열에 넣는다.
return item.title.toLowerCase(); // 그리고 그 아이템들에서 영화제목을 소문자로 변환한다.
});
// query와 title_list 비교
let find_title = title_list.filter((item) => { // 소문자로변환된 검색엔진박스 내 value값 vs API 데이터에서 뽑아서 소문자로 변환한 영화제목 값 비교
return item.includes(query); // api 데이터 영화제목에서 검색엔진 입력값이 포함되어 있으면 함수에서 나오기
});
// 전체title에서 title 인덱스번호 찾기
let find_index = []; // 찾은 영화 인덱스 정보 배열 새로 만들기
for (let i in find_title) { // 검색엔진 값으로 이루어진 배열 요소 하나씩 돌기
let idx = title_list.findIndex((item) => { // API에서 나온 영화제목 배열 요소와 검색엔진이 같은 값의 인덱스 정보 값
return item === find_title[i]; // 이면 빠져나오기
});
find_index.push(idx); // 찾은 영화 인덱스 정보 배열에 같은 값 추가
}
// 유효성 검사 : 만약 값이 없다면 -> alert
if (find_index.length === 0) {
alert('검색 결과가 없습니다.');
// 값이 있으면 -> 전체 데이터에서 일치한 데이터 뽑아와서 리스트 만들기
} else {
const match_movie = []; // 서로 값이 매치한 영화제목 배열 새로 만들기
for (let a of find_index) { // 값이 일치했던 영화제목 인덱스 배열을 돌면서
const movies = dataIndex[a]; // 영화제목값
match_movie.push(movies); // 매치한 영화제목 배열에 영화제목값을 넣는다
}
cardList.innerHTML = ""; // 데이터 정보를 넣어줄 공간을 비우기
match_movie.forEach((result) => { // 매치한 영화제목 배열을 하나씩 돌면서
const title = result['title'];
const overview = result['overview'];
const posterPath = result['poster_path'];
const voteAverage = result['vote_average'];
const id = result['id'];
// 아래 내용을 넣어라
const temp_html = `
<div data-id="${id}" class="movieCard" >
<img class="movieImage" src="${IMAGE_BASE_URL}${posterPath}" alt="">
<div class="movieName">
<h3>${title}</h3>
<p class="movieGrade">Rating : ${voteAverage}</p>
</div>
<p class="movieExplanation">${overview}</p>
</div>
`;
cardList.insertAdjacentHTML('beforeend', temp_html); // temp_html 데이터 정보를 넣어줄 공간안에서 마지막 자식 이후에 위치시키기
});
};
movieIdShowbyClick(); // 검색 후에 나온 영화정보 카드를 클릭했을 때 ID 값 나오게하기
};
// 검색 기능
searchArea.addEventListener("submit", movieSearch); // 검색엔진을 품고 있는 formtag에서 "submit"이 일어나면 오른쪽인자 함수를 실행시켜라
}
fetch(API_url, options) // API Data url을 통해
.then(response => response.json()) // 약속이 이행되면 response 를 response 안에 json 형태로 데이터를 가져오기
.then(data => fetchSecondThen(data)) // 가져온 데이터를 아래처럼 실행하기 (적용)
.catch(err => console.error(err));
// -------------- 페이지 홈으로 가기 ----------------- //
const goHomeBtn = document.getElementById('go_home_btn');
goHomeBtn.addEventListener('click', (event) => {
window.location.href = 'index.html';
});
const refreshBtn = document.getElementById('refresh_icon');
refreshBtn.addEventListener('click', (event) => {
window.location.href = 'index.html';
});
// -------------- 홈페이지 정보 alert ----------------- //
const infoBtn = document.getElementById('info_icon');
infoBtn.addEventListener('click', (event) => {
window.alert('Welcome to FindMovie. I wish you have a great time searching for movies that you like to discover!');
});
_______________
+ submit 이벤트는 html의 form tag 그리고 그 안에 input tag 으로 구성해놔야 작동하는 것 같다.
전체코드 (HTML)
더보기
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>FindMovie</title>
<!--CSS 연결-->
<link rel="stylesheet" href="./style.css">
<!--jQuery 사용-->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
</head>
<body>
<header>
<a id="go_home_btn" href="#"><img src="./asset/FindMovie_logo.png"></a>
<div class="search_area">
<div class="searchbox">
<div type="text" class="type_area">
<form id="engin_button_box" class="engine_button_box">
<form class="search_area" id="search_area">
<input id="search_input" class="typebox" type="text" placeholder="Search" autocomplete="off" required />
<button id="search_btn" class="search_btn">
<img id="search_icon" src="./asset/search_icon_50.png"/>
<img id="refresh_icon" src="./asset/refresh_icon_64.png"/>
</button>
</form>
</form>
</div>
</div>
</div>
<div class="sub_option_area">
<img id="info_icon" src="./asset/information-50 .png"/>
</div>
</header>
<main>
<div id="list_area" class="list_area">
</div>
</main>
<!--JavaScript 연결-->
<script type="text/javascript" src="script.js"></script>
</body>
</html>
_______________
전체코드 (CSS)
더보기
html {
scroll-behavior: smooth;
}
html, body {
margin: 0;
padding: 0;
overflow-x: hidden;
background-color: black;
color: white;
}
/*----------------------- header ----------------------- */
header {
display: flex;
flex-direction: row;
align-items:center;
justify-content: space-between;
width: 100%;
height: 7rem;
/* border: 1px solid red; */
}
header > img {
align-self: flex-start;
width: 17rem;
margin-right: 10px;
margin-top: 10px;
margin-left: 10px;
}
.engine_button_box {
display: flex;
flex-direction: column;
align-items: center;
justify-content: end;
height: 7rem;
width: 50rem;
/* border: 1px solid pink; */
}
.search_area {
display: flex;
flex-direction: column;
align-items: center;
justify-content: start;
height: 7rem;
margin-top: 0;
/* border: 1px solid red;; */
}
.searchbox {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 5rem;
margin-top: 14px;
/* border: 1px solid green; */
}
.type_area > form{
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
width: 40rem;
/* border: solid 1px blue; */
}
.type_area > form > input:focus{
border-color: yellow;
outline: none;
}
.typebox {
display: flex;
flex-direction: row;
align-items: center;
justify-content: end;
border-color: white;
width: 40rem;
height: 2.3rem;
border: 0.1px solid white;
margin: 5px 5px 5px 5px;
background-color: black;
padding: 1px 15px 1px 15px;
color: white;
font-size: 1rem;
}
.search_btn {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
height: 2.8rem;
background-color: transparent;
border: none;
}
#search_icon {
height: 2.8rem;
margin-left: 3px;
cursor: pointer;
}
#refresh_icon {
height: 2.2rem;
margin-left: 5px;
cursor: pointer;
}
.sub_option_area {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
width: 10rem;
}
#info_icon {
height: 2.2rem;
align-self: flex-end;
margin: 0 10px 2px 10px;
cursor: pointer;
}
/* info icon from
<a target="_blank" href="https://icons8.com/icon/37303/information">Information</a> icon by <a target="_blank" href="https://icons8.com">Icons8</a> */
/* search incon from
<a target="_blank" href="https://icons8.com/icon/12456/google-web-search">Google Web Search</a> icon by <a target="_blank" href="https://icons8.com">Icons8</a>*/
/* refresh incon from
<a target="_blank" href="https://icons8.com/icon/4aaibf6SmbMJ/reboot">Refresh</a> icon by <a target="_blank" href="https://icons8.com">Icons8</a>*/
/*----------------------- main ----------------------- */
.list_area {
display: flex;
flex-direction: row;
justify-content: center;
flex-wrap: wrap;
margin: 130px 50px 100px 50px;
}
.movieCard {
display: flex;
flex-direction: column;
align-items:left;
justify-content:start;
width: 14rem;
height: 41rem;
margin: 20px 20px 80px 20px;
padding: 0 0 5px 0;
border-radius: 20px;
transition: transform .5s;
cursor: pointer;
}
.movieCard:hover {
transform: scale(1.03);
}
.movieImage {
width: 14rem;
height:18rem;
object-fit: cover;
border-radius: 20px;
}
.movieCard > p {
font-size: 0.95rem;
margin: 45px 3px 0 3px;
}
.movieName{
display: flex;
flex-direction: column;
justify-content: start;
align-items: left;
height: 3.5rem;
padding: 8px 1px 1px 0;
/* border: 1px solid pink; */
}
.movieName > h3 {
font-size: 1.1rem;
margin-left: 5px;
margin-bottom: 5px;
}
.movieName > p {
font-size: 0.9rem;
color: yellow;
margin: 0 0 5px 5px;
}
_______________
전체코드 (JS)
더보기
const searchArea = document.querySelector('.search_area');
const IMAGE_BASE_URL = "https://image.tmdb.org/t/p/w500/";
const movieCard = document.querySelector(".movieCard");
const movieImage = document.querySelector(".movieImage");
const searchInput = document.getElementById("search_input")
.addEventListener("keyup", function(e) {
if (e.code === 'Enter') {
document.getElementById("submit").click();
}
});
// --------------TMDB API: API request and get ----------------- //
const options = {
method: 'GET',
headers: {
accept: 'application/json',
Authorization: 'Bearer eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiIzZjI5M2EzNGQ5YTdhYmE3ZmE2YjA1MmVkNDBhNzAzNSIsInN1YiI6IjY1MmY3YTM2MDI0ZWM4MDEwMTU0MzQ3NCIsInNjb3BlcyI6WyJhcGlfcmVhZCJdLCJ2ZXJzaW9uIjoxfQ.4UlNrO-aIaz-15g60eboaP-Zr-ck8iYXpu1qfhKZm0E'
}
};
const API_url = "https://api.themoviedb.org/3/movie/top_rated?language=en-US&page=1"; // url이 길기때문에 변수안에 넣어줌
function fetchSecondThen(data) {
// ---------- 초기 세팅 ----------- //
let dataIndex = data['results']; // 우리가 앞으로 쓸 영화데이터 = 데이터(data)의 안에 있는 results 값 :result는 [ { "keyname" : "value", "keyname" : [ 배열 value ] } ] 형태로 묶여있다.
// ----------- 영화데이터 보여주기 ----------- //
const cardList = document.querySelector('.list_area'); // 받아온 데이터를 넣어줄 공간
cardList.innerHTML = ""; // 받아온 데이터를 넣어줄 공간 안에 html 내용 초기화
dataIndex.forEach((i) => { // i 는 index. 영화데이터 묶음에 있는 모든 요소를 돈다.
let myTitle = i['title']; // i번째 인덱스, 객체안에 요소 'title'
let myOverView = i['overview'];
let myPosterPath = i['poster_path'];
let myVoteAverage = i['vote_average'];
let myId = i['id'];
// html에 넣어지는 tag + 콘텐츠
let temp_html = `
<div data-id="${myId}" class="movieCard" >
<img class="movieImage" src="${IMAGE_BASE_URL}${myPosterPath}" alt="">
<div class="movieName">
<h3>${myTitle}</h3>
<p class="movieGrade">Rating : ${myVoteAverage}</p>
</div>
<p class="movieExplanation">${myOverView}</p>
</div>`;
cardList.insertAdjacentHTML('beforeend', temp_html); // temp_html을 데이터 넣어줄 공간안에서 마지막 자식 이후에 위치시키기
});
// ------------- 영화 카드 클릭시 ID 보여주기 ----------- //
const movieIdShowbyClick = function () {
const movieCards = document.querySelectorAll(".movieCard"); // 영화카드 모두 찾아 선택
movieCards.forEach(card => { // 영화카드들의 배열을 한번씩 모두 함수에 대입
card.addEventListener("click", function () { // "이벤트명", 함수 이하의 동작을 하는 이벤트 등록
let movieCardId = this.getAttribute("data-id"); // 영화카드클래스 안에 속성 data-id 값
alert(`영화 ID : ${movieCardId}`); // 영화 data id 값 alert
})
})
}
movieIdShowbyClick(); // 실행
// -------- 영화 검색 ----------- //
function movieSearch(event) { // 영화검색기능
event.preventDefault(); // submit 되는 동시에 창 새로고침을 방지
const searchInput = document.getElementById('search_input'); // 검색엔진박스
const query = searchInput.value.toLowerCase(); // 검색엔진박스 내 value 값을 소문자로 변환
searchInput.value = ""; // 검색엔진박스 기본 value값은 빈문자열
console.log(query);
if (query.trim() === '') { // a문자열 양 끝의 공백을 제거 -> 소문자로변환된 value값이 빈문자열이면
alert('검색어를 입력해주세요'); // 아래 alert 뜨게 해라
return; // 리턴은 함수를 즉시 빠져 나오는 기능
}
// API 데이터를 배열로 가져오기
let title_list = dataIndex.map((item) => { // 영화데이터 배열을 그대로 복사해 새로운 배열에 넣는다.
return item.title.toLowerCase(); // 그리고 그 아이템들에서 영화제목을 소문자로 변환한다.
});
// query와 title_list 비교
let find_title = title_list.filter((item) => { // 소문자로변환된 검색엔진박스 내 value값 vs API 데이터에서 뽑아서 소문자로 변환한 영화제목 값 비교
return item.includes(query); // api 데이터 영화제목에서 검색엔진 입력값이 포함되어 있으면 함수에서 나오기
});
// 전체title에서 title 인덱스번호 찾기
let find_index = []; // 찾은 영화 인덱스 정보 배열 새로 만들기
for (let i in find_title) { // 검색엔진 값으로 이루어진 배열 요소 하나씩 돌기
let idx = title_list.findIndex((item) => { // API에서 나온 영화제목 배열 요소와 검색엔진이 같은 값의 인덱스 정보 값
return item === find_title[i]; // 이면 빠져나오기
});
find_index.push(idx); // 찾은 영화 인덱스 정보 배열에 같은 값 추가
}
// 유효성 검사 : 만약 값이 없다면 -> alert
if (find_index.length === 0) {
alert('검색 결과가 없습니다.');
// 값이 있으면 -> 전체 데이터에서 일치한 데이터 뽑아와서 리스트 만들기
} else {
const match_movie = []; // 서로 값이 매치한 영화제목 배열 새로 만들기
for (let a of find_index) { // 값이 일치했던 영화제목 인덱스 배열을 돌면서
const movies = dataIndex[a]; // 영화제목값
match_movie.push(movies); // 매치한 영화제목 배열에 영화제목값을 넣는다
}
cardList.innerHTML = ""; // 데이터 정보를 넣어줄 공간을 비우기
match_movie.forEach((result) => { // 매치한 영화제목 배열을 하나씩 돌면서
const title = result['title'];
const overview = result['overview'];
const posterPath = result['poster_path'];
const voteAverage = result['vote_average'];
const id = result['id'];
// 아래 내용을 넣어라
const temp_html = `
<div data-id="${id}" class="movieCard" >
<img class="movieImage" src="${IMAGE_BASE_URL}${posterPath}" alt="">
<div class="movieName">
<h3>${title}</h3>
<p class="movieGrade">Rating : ${voteAverage}</p>
</div>
<p class="movieExplanation">${overview}</p>
</div>
`;
cardList.insertAdjacentHTML('beforeend', temp_html); // temp_html 데이터 정보를 넣어줄 공간안에서 마지막 자식 이후에 위치시키기
});
};
movieIdShowbyClick(); // 검색 후에 나온 영화정보 카드를 클릭했을 때 ID 값 나오게하기
};
// 검색 기능
searchArea.addEventListener("submit", movieSearch); // 검색엔진을 품고 있는 formtag에서 "submit"이 일어나면 오른쪽인자 함수를 실행시켜라
}
fetch(API_url, options) // API Data url을 통해
.then(response => response.json()) // 약속이 이행되면 response 를 response 안에 json 형태로 데이터를 가져오기
.then(data => fetchSecondThen(data)) // 가져온 데이터를 아래처럼 실행하기 (적용)
.catch(err => console.error(err));
// -------------- 페이지 홈으로 가기 ----------------- //
const goHomeBtn = document.getElementById('go_home_btn');
goHomeBtn.addEventListener('click', (event) => {
window.location.href = 'index.html';
});
const refreshBtn = document.getElementById('refresh_icon');
refreshBtn.addEventListener('click', (event) => {
window.location.href = 'index.html';
});
// -------------- 홈페이지 정보 alert ----------------- //
const infoBtn = document.getElementById('info_icon');
infoBtn.addEventListener('click', (event) => {
window.alert('Welcome to FindMovie. I wish you have a great time searching for movies that you like to discover!');
});
_______________
내가 쓴 메소드
findIndex()
string.prototype.trim()
event.preventDefault()
홈화면으로가기/새로고침
마우스 이벤트
Enter 키, 버튼 제출로 event 만들기
728x90
반응형
'부트캠프 개발일지 2023-2024 > Bootcamp 생활기록' 카테고리의 다른 글
[4주차] html parsing, DOM tree, form-input 태그, cmd 함수로 바로가기 (0) | 2023.10.23 |
---|---|
[4주차] 첫 슬럼프 (2) | 2023.10.23 |
[3주차] Promise( ), fetch( ), then( ), async( ), await( ), mouseover (0) | 2023.10.19 |
[3주차] JavaScript 개인과제: TMDB API 데이터 불러오기(fetch), 화면에 띄우기 +@ (1) | 2023.10.18 |
[3주차] 알고리즘 특강 (2) (0) | 2023.10.17 |