67일차 (1) - JavaScript (실습문제 Modal 추가, fetchAPI - GET, POST)

Yohan·2024년 5월 28일
0

코딩기록

목록 보기
101/156
post-custom-banner

실습문제 Modal에 상세조회 추가

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>

    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">

    <style>
        * {
            margin: 0;
            padding: 0;
        }
        ul {
            list-style: none;
        }
        a {
            color: inherit;
            text-decoration: none;
        }
        .movie-list {
            width: 80%;
            margin: 0 auto;
        }
        .movie-list .movie {
            float: left;
            width: 23%;
            height: 500px;
            margin-right: 2%;
            margin-bottom: 10px;
            border: 1px solid gray;
            box-sizing: border-box;
            padding: 10px;
        }
        .movie-list .movie .img-box {
            width: 100%;
            height: 70%;
            overflow: hidden;
        }
        .movie-list .movie .img-box img {
            width: 100%;
        }
        .movie-list .movie .inner {
            padding: 25px 15px;
        }
        .movie-list .movie .inner * {
            font-size: 1.3em;
            font-weight: 700;
            margin-bottom: 5px;
        }

        .clearfix::after {
            content: '';
            display: block;
            clear: both;
        }

        header {
            width: 100%;
            padding: 10px 120px;
            box-sizing: border-box;
            border-bottom: 2px solid gray;
            margin-bottom: 20px;
            display: flex;
            justify-content: space-between;
        }
        header h1 {
            flex: 1;
        }
        header .gnb {
            flex: 1;
            margin-top: 10px;
        }
        header .gnb ul {
            display: flex;
            justify-content: space-evenly;
            align-items: center;
        }

    </style>

</head>
<body>

    <header>
        <h1>WhatSsa!!</h1>
        <nav class="gnb">
            <ul>
                <li><a id="s_rate" href="#">다운로드순</a></li>
                <li><a id="s_year" href="#">발매연도순</a></li>
                <li><a id="s_like" href="#">좋아요순</a></li>
            </ul>
        </nav>
    </header>


    <div class="movie-list clearfix">
        <!-- <div class="movie">
            <div class="img-box">
                <img src="https://yts.mx/assets/images/movies/doctor_who_the_day_of_the_doctor_2013/large-cover.jpg" alt="표지사진">
            </div>
            <div class="inner">
                <div class="title">제목</div>
                <div class="year">개봉년도</div>
                <div class="rating">평점</div>
            </div>
        </div> -->
        
        
    </div>

    
    <!-- Modal -->
    <div class="modal fade" id="detailModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
        <div class="modal-dialog">
        <div class="modal-content">
            <div class="modal-header">
                <h5 class="modal-title" id="exampleModalLabel">아바타</h5>
                <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
            </div>
            <div class="modal-body">
                
                <img class="desc-image" src="" alt="" style="width:200px;float: left; margin-right: 20px;">
                <p class="movie-description"></p>

            </div>
            <div class="modal-footer">
            <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
            </div>
        </div>
        </div>
    </div>
    
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>

    <script>


      function makeMovieListDOM(movies) {
        
        let tag = '';

        movies.forEach(({id, large_cover_image, title, rating, year}) => {
          tag += `
          <div class="movie" data-movie-id="${id}" data-bs-toggle="modal" data-bs-target="#detailModal">
            <div class="img-box">
                <img src="${large_cover_image}" alt="표지사진">
            </div>
            <div class="inner">
                <div class="title">${title}</div>
                <div class="year">${year}</div>
                <div class="rating">${rating}</div>
            </div>
          </div>
          `;
        });

        document.querySelector('.movie-list').innerHTML = tag;

      }

      // 영화 API를 호출하는 함수 (기본값: 다운로드 수)
      function fetchMovies(condition='download_count') {

        fetch(`https://yts.mx/api/v2/list_movies.json?sort_by=${condition}`)
          .then(response => response.json())
          .then(json => {
            console.log(json.data.movies);
            makeMovieListDOM(json.data.movies);
          });
      }

      // 초기영화 정보 로딩
      fetchMovies();

      // a태그 클릭이벤트
      document.querySelector('header .gnb ul').addEventListener('click', e => {
        // a태그 링크이동 기능 중지
        e.preventDefault();
        const id = e.target.id;
        switch (id) {
          case 's_rate':
            fetchMovies('download_count');
            break;
          case 's_year':
            fetchMovies('year');
            break;
          case 's_like':
            fetchMovies('like_count');
            break;
        }
      });


      // 영화 상세정보를 모달에 렌더링
      function makeMovieDetailDOM({ 
        title, large_cover_image, description_full 
      }) {
        const $modalTitle = document.querySelector('.modal-title');
        const $descImg = document.querySelector('.desc-image');
        const $summary = document.querySelector('.movie-description');

        $modalTitle.textContent = title;
        $summary.textContent = description_full;
        $descImg.src = large_cover_image;
      }


      const $movie = document.querySelector('.movie-list');

      $movie.addEventListener('click', e => {
        if (!e.target.matches('.movie-list .movie *')) return; // 틀 외의 것을 클릭 못하게
        // console.log('영화 클릭@');

        // console.log(e.target.closest('.movie').dataset.movieId);
        const movieId = e.target.closest('.movie').dataset.movieId;
        fetch(`https://yts.mx/api/v2/movie_details.json?movie_id=${movieId}`)
          .then(res => res.json())
          .then(json => {
            makeMovieDetailDOM(json.data.movie);
          });

      });

    </script>
    
</body>
</html>

fetchAPI POST

  • body와 header 필요 O, 전달할 것 필요 O
<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  
  <div>
    댓글내용: <input type="text" id="reply-text">
  </div>
  <div>
    작성자: <input type="text" id="reply-writer">
  </div>
  <div>
    <button id="register">등록</button>
  </div>

  <p id="reply"></p>

  <script>
    // GET방식을 제외한 나머지 것들은 두 번째 파라미터 필요
    document.getElementById('register').onclick = e => {
      const requestInfo = {
        method: 'POST',
        headers: { // 요청 헤더 정보를 key, value 로 묶음
          'content-type': 'application/json'
        },
        body: JSON.stringify({ /// JS -> JSON으로 변환해서 보냄
          text: document.getElementById('reply-text').value,
          author: document.getElementById('reply-writer').value,
          bno: 102
        })
      };

      fetch('http://localhost:8383/api/v1/replies', requestInfo)
        .then(res => res.json())
        .then(json => {
          document.getElementById('reply').innerHTML = `
            ${json[0].writer}
          `;
        })
    };


  </script>
</body>
</html>

fetchAPI DELETE

  • bodyd와 header 필요 X, 전달할 것 X
<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  
  <button id="remove">삭제!</button>

  <script>
    document.getElementById('remove').onclick = e => {

      const rno = 5016;
      fetch(`http://localhost:8383/api/v1/replies/${rno}`, {
        method: 'DELETE'
      })
        .then(res => res.json())
        .then(json => {
          console.log(json);
        })
    };
  </script>

</body>
</html>

async, await

async는 함수의 선언 앞에 위치하며, 해당 함수가 비동기로 동작하도록함

await 연산자는 async 함수 내에서만 사용할 수 있으며, 프로미스의 완료를 기다린 후 결과 값을 반환

장점

1. async와 await를 사용하면 비동기 코드를 마치 동기 코드처럼 읽고 작성가능

-> 코드의 가독성과 유지보수성을 향상

2. async, await 사용함으로써 fetch에서의 then, then, then... 으로 진행되는 과정 생략 가능

-> promise대신 promise 안에있는 promiseResult를 한 번에 볼 수 있음

	// 일일히 then을 써서 확인 해야하는 한계
    function fetchGet() {
      const result = fetch('http://localhost:8383/api/v1/replies/100');
      // console.log(result);

      const json = result.then(res => res.json());
      // console.log(json);

      const data = json.then(data => {
        console.log('then내부:', data);
        return '하하호호';
      });
      console.log('then외부:', data);
    }

    // async함수는 기본적으로 Promise를 소비하는 함수
    async function fetchGet2() {
      // await : then 메서드를 대신 호출해서 Promise를 거치지 않고
      //         PromiseResult를 바로 가져옴
      const result = await fetch('http://localhost:8383/api/v1/replies/100');
      // console.log(result);

      const json = await result.json();
      console.log(json);
    }

    document.getElementById('show').onclick = e => {
      fetchGet2();
    };

게시판 프로그램 댓글 CSR로 변경

  • 게시판 SSR로, 댓글은 SSR이 아닌 비동기 (CSR)로 수정하기 위해서 게시글 불러올 때 댓글 목록을 같이 불러오는 것 삭제
  • detail.jsp에서도 삭제

댓글 목록 뜨게 하기

detail.jsp에 댓글목록 html 생성

댓글 페이징

mybatis 주의사항

  • mybatis 는 함수의 파라미터가 두개 이상이면 잘 작동 X
    -> 두 개 이상 넣어야 하는 경우, @Param 어노테이션을 사용해서 별칭 부여
  • ReplyMapper

ReplyListDto 생성

  • 원래있던 ReplyDetailDto를 필드로하는 ReplyListDto를 만듦
    -> 배열의 이름이 생성되어 사용시에 편리!, 아래는 replies라는 이름으로 생성

  • 원래쓰던 DTO를 바꿨으므로 쓰였던 DTO 모두 수정

ReplyMapper.xml

  • 페이징을 위해 LIMIT 추가
    -> ReplyMapper에서 @Param 설정 했으므로 p.으로 접근

ReplyService

  • DTO를 수정했으므로 Service에서도 수정
    -> 댓글 목록과, 페이지 정보를 모두 들고 있는 ReplyListDto 반환

ReplyController

  • 댓글 목록 조회시 URL 수정
  • 조회시의 페이징 설정 추가

profile
백엔드 개발자
post-custom-banner

0개의 댓글