프로젝트 설계하기

    필요 기능

    • 키워드로 상품 검색하고 그 결과를 목록으로 보여주기
    • 관심 상품 등록하기
    • 관심 상품 조회하기
    • 관심 상품에 원하는 가격 등록하고, 그 가격보다 낮은 경우 표시하기

     

    HTML, 이미지 파일 준비

    • 파일 분리
      ✔️ HTML 파일이 CSS 와 Javascpript 때문에 지나치게 길어지는 것을 방지하고 가독성을 높이기 위함
      ✔️ .css 와 .js 로 끝나는 파일 생성 후 link 와 script 태그로 각 파일을 불러오면 index.html 파일에 모두 작성한 것과 동일하게 작동
      1. src > main > resources > static 에 파일 생성
        • index.html
          <!doctype html>
          <html lang="en">
          <head>
              <meta charset="UTF-8">
              <meta name="viewport"
                    content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
              <meta http-equiv="X-UA-Compatible" content="ie=edge">
              <link rel="stylesheet" href="style.css">
              <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
              <script src="basic.js"></script>
              <title>나만의 셀렉샵</title>
          </head>
          <body>
          <div class="header">
              Select Shop
          </div>
          <div class="nav">
              <div class="nav-see active">
                  모아보기
              </div>
              <div class="nav-search">
                  탐색하기
              </div>
          </div>
          <div id="see-area">
              <div id="product-container">
                  <div class="product-card" onclick="window.location.href='https://spartacodingclub.kr'">
                      <div class="card-header">
                          <img src="https://shopping-phinf.pstatic.net/main_2085830/20858302247.20200602150427.jpg?type=f300"
                               alt="">
                      </div>
                      <div class="card-body">
                          <div class="title">
                              Apple 아이폰 11 128GB [자급제]
                          </div>
                          <div class="lprice">
                              <span>919,990</span>원
                          </div>
                          <div class="isgood">
                              최저가
                          </div>
                      </div>
                  </div>
              </div>
          </div>
          <div id="search-area">
              <div>
                  <input type="text" id="query">
                  <!--    <img src="images/icon-search.png" alt="">-->
              </div>
              <div id="search-result-box">
                  <div class="search-itemDto">
                      <div class="search-itemDto-left">
                          <img src="https://shopping-phinf.pstatic.net/main_2399616/23996167522.20200922132620.jpg?type=f300" alt="">
                      </div>
                      <div class="search-itemDto-center">
                          <div>Apple 아이맥 27형 2020년형 (MXWT2KH/A)</div>
                          <div class="price">
                              2,289,780
                              <span class="unit">원</span>
                          </div>
                      </div>
                      <div class="search-itemDto-right">
                          <img src="images/icon-save.png" alt="" onclick='addProduct()'>
                      </div>
                  </div>
              </div>
              <div id="container" class="popup-container">
                  <div class="popup">
                      <button id="close" class="close">
                          X
                      </button>
                      <h1>⏰최저가 설정하기</h1>
                      <p>최저가를 설정해두면 선택하신 상품의 최저가가 떴을 때<br/> 표시해드려요!</p>
                      <div>
                          <input type="text" id="myprice" placeholder="200,000">원
                      </div>
                      <button class="cta" onclick="setMyprice()">설정하기</button>
                  </div>
              </div>
          </div>
          </body>
          </html>
        • basic.js
          let targetId;
          
          // $(document).ready: 페이지가 모두 로드된 직후 실행할 자바스크립트 코드를 넣는 곳
          $(document).ready(function () {
              // id 가 query 인 녀석 위에서 엔터를 누르면 execSearch() 함수를 실행하라는 뜻입니다.
              $('#query').on('keypress', function (e) {
                  if (e.key == 'Enter') {
                      execSearch();
                  }
              });
              $('#close').on('click', function () {
                  $('#container').removeClass('active');
              })
          
              $('.nav div.nav-see').on('click', function () {
                  $('div.nav-see').addClass('active');
                  $('div.nav-search').removeClass('active');
          
                  $('#see-area').show();
                  $('#search-area').hide();
              })
              $('.nav div.nav-search').on('click', function () {
                  $('div.nav-see').removeClass('active');
                  $('div.nav-search').addClass('active');
          
                  $('#see-area').hide();
                  $('#search-area').show();
              })
          
              $('#see-area').show();
              $('#search-area').hide();
          
              showProduct();  // 일단 접속하면 관심 상품을 보여주어야 하기 때문에 showProduct() 함수 호출
          })
          
          
          function numberWithCommas(x) {
              return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
          }
          
          //////////////////////////////////////////////////////////////////////////////////////////
          ///// 여기 아래에서부터 코드를 작성합니다. ////////////////////////////////////////////////////////
          //////////////////////////////////////////////////////////////////////////////////////////
          
          function execSearch() {
              /**
               * 검색어 input id: query
               * 검색결과 목록: #search-result-box
               * 검색결과 HTML 만드는 함수: addHTML
               */
          
              // 검색 결과 비우기
              $('#search-result-box').empty();
          
              // 1. 검색창의 입력값을 가져온다.
              let query = $('#query').val();
          
              // 2. 검색창 입력값을 검사하고, 입력하지 않았을 경우 focus.
              if (query == '') {
                  alert('검색어를 입력해주세요');
                  $('#query').focus();
              }
          
              // 3. GET /api/search?query=${query} 요청
              $.ajax({
                  type: 'GET',
                  url: `/api/search?query=${query}`,
                  success: function (response) {
                      // 4. for 문마다 itemDto를 꺼내서 HTML 만들고 검색결과 목록에 붙이기!
                      for (let i = 0; i < response.length; i++) {
                          let itemDto = response[i];
                          let tempHtml = addHTML(itemDto);
                          $('#search-result-box').append(tempHtml);
                      }
                  }
              })
          
          }
          
          function addHTML(itemDto) {
              /**
               * class="search-itemDto" 인 녀석에서
               * image, title, lprice, addProduct 활용하기
               * 참고) onclick='addProduct(${JSON.stringify(itemDto)})'
               */
              return `<div class="search-itemDto">
                  <div class="search-itemDto-left">
                      <img src="${itemDto.image}" alt="">
                  </div>
                  <div class="search-itemDto-center">
                      <div>${itemDto.title}</div>
                      <div class="price">
                          ${numberWithCommas(itemDto.lprice)}
                          <span class="unit">원</span>
                      </div>
                  </div>
                  <div class="search-itemDto-right">
                      <img src="images/icon-save.png" alt="" onclick='addProduct(${JSON.stringify(itemDto)})'>
                  </div>
              </div>`
          }
          
          function addProduct(itemDto) {
              /**
               * modal 뜨게 하는 법: $('#container').addClass('active');
               * data를 ajax로 전달할 때는 두 가지가 매우 중요
               * 1. contentType: "application/json",
               * 2. data: JSON.stringify(itemDto),
               */
              // 1. POST /api/products 에 관심 상품 생성 요청
              $.ajax({
                  type: 'POST',
                  url: '/api/products',
                  data: JSON.stringify(itemDto),
                  contentType: 'application/json',
                  success: function (response) {
                      // 2. 응답 함수에서 modal을 뜨게 하고, targetId 를 response.id 로 설정 (숙제로 myprice 설정하기 위함)
                      $('#container').addClass('active');
                      targetId = response.id;
                  }
              })
          }
          
          function showProduct() {
              /**
               * 관심상품 목록: #product-container
               * 검색결과 목록: #search-result-box
               * 관심상품 HTML 만드는 함수: addProductItem
               */
              // 1. GET /api/products 요청
              $.ajax({
                  type: 'GET',
                  url: '/api/products',
                  success: function (response) {
                      // 2. 관심상품 목록, 검색결과 목록 비우기
                      $('#product-container').empty();
                      $('#search-result-box').empty();
                      // 3. for 문마다 관심 상품 HTML 만들어서 관심상품 목록에 붙이기!
                      for (let i = 0; i < response.length; i++) {
                          let product = response[i];
                          let tempHtml = addProductItem(product);
                          $('#product-container').append(tempHtml);
                      }
                  }
              })
          }
          
          function addProductItem(product) {
              // link, image, title, lprice, myprice 변수 활용하기
              return `<div class="product-card" onclick="window.location.href='${product.link}'">
                      <div class="card-header">
                          <img src="${product.image}"
                               alt="">
                      </div>
                      <div class="card-body">
                          <div class="title">
                              ${product.title}
                          </div>
                          <div class="lprice">
                              <span>${numberWithCommas(product.lprice)}</span>원
                          </div>
                          <div class="isgood ${product.lprice > product.myprice ? 'none' : ''}">
                              최저가
                          </div>
                      </div>
                  </div>`;
          }
          
          function setMyprice() {
              /**
               * 숙제! myprice 값 설정하기.
               * 1. id가 myprice 인 input 태그에서 값을 가져온다.
               * 2. 만약 값을 입력하지 않았으면 alert를 띄우고 중단한다.
               * 3. PUT /api/product/${targetId} 에 data를 전달한다.
               *    주의) contentType: "application/json",
               *         data: JSON.stringify({myprice: myprice}),
               *         빠뜨리지 말 것!
               * 4. 모달을 종료한다. $('#container').removeClass('active');
               * 5, 성공적으로 등록되었음을 알리는 alert를 띄운다.
               * 6. 창을 새로고침한다. window.location.reload();
               */
          
              // 1. id가 myprice 인 input 태그에서 값을 가져온다.
              let myprice = $('#myprice').val();
          
              // 2. 만약 값을 입력하지 않았으면 alert를 띄우고 중단한다.
              if (myprice == '') {
                  alert("올바른 가격을 입력해주세요");
                  return;
              }
              // 3. PUT /api/products/${targetId} 에 data를 전달한다.
              $.ajax({
                  type: "PUT",
                  url: `/api/products/${targetId}`,
                  contentType: "application/json",
                  data: JSON.stringify({myprice: myprice}),
                  success: function (response) {
                      // 4. 모달을 종료한다. $('#container').removeClass('active');
                      $('#container').removeClass('active');
                      // 5, 성공적으로 등록되었음을 알리는 alert를 띄운다.
                      alert("성공적으로 등록되었습니다.");
                      // 6. 창을 새로고침한다. window.location.reload();
                      window.location.reload();
                  }
              })
          
          
          }
        • style.css
          body {
              margin: 0px;
          }
          
          #search-result-box {
              margin-top: 15px;
          }
          
          .search-itemDto {
              width: 530px;
              display: flex;
              flex-direction: row;
              align-content: center;
              justify-content: space-around;
          }
          
          .search-itemDto-left img {
              width: 159px;
              height: 159px;
          }
          
          .search-itemDto-center {
              display: flex;
              flex-direction: column;
              align-items: center;
              justify-content: space-evenly;
          }
          
          .search-itemDto-center div {
              width: 280px;
              height: 23px;
              font-size: 18px;
              font-weight: normal;
              font-stretch: normal;
              font-style: normal;
              line-height: 1.3;
              letter-spacing: -0.9px;
              text-align: left;
              color: #343a40;
              overflow: hidden;
              white-space: nowrap;
              text-overflow: ellipsis;
          }
          
          .search-itemDto-center div.price {
              height: 27px;
              font-size: 27px;
              font-weight: 600;
              font-stretch: normal;
              font-style: normal;
              line-height: 1;
              letter-spacing: -0.54px;
              text-align: left;
              color: #E8344E;
          }
          
          .search-itemDto-center span.unit {
              width: 17px;
              height: 18px;
              font-size: 18px;
              font-weight: 500;
              font-stretch: normal;
              font-style: normal;
              line-height: 1;
              letter-spacing: -0.9px;
              text-align: center;
              color: #000000;
          }
          
          .search-itemDto-right {
              display: inline-block;
              height: 100%;
              vertical-align: middle
          }
          
          .search-itemDto-right img {
              height: 25px;
              width: 25px;
              vertical-align: middle;
              margin-top: 60px;
              cursor: pointer;
          }
          
          input#query {
              padding: 15px;
              width: 526px;
              border-radius: 2px;
              background-color: #e9ecef;
              border: none;
          
              background-image: url('images/icon-search.png');
              background-repeat: no-repeat;
              background-position: right 10px center;
              background-size: 20px 20px;
          }
          
          input#query::placeholder {
              padding: 15px;
          }
          
          button {
              color: white;
              border-radius: 4px;
              border-radius: none;
          }
          
          .popup-container {
              display: none;
              position: fixed;
              top: 0;
              left: 0;
              bottom: 0;
              right: 0;
              background-color: rgba(0, 0, 0, 0.5);
              align-items: center;
              justify-content: center;
          }
          
          .popup-container.active {
              display: flex;
          }
          
          .popup {
              padding: 20px;
              box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.3);
              position: relative;
              width: 370px;
              height: 190px;
              border-radius: 11px;
              background-color: #ffffff;
          }
          
          .popup h1 {
              margin: 0px;
              font-size: 22px;
              font-weight: 500;
              font-stretch: normal;
              font-style: normal;
              line-height: 1;
              letter-spacing: -1.1px;
              color: #000000;
          }
          
          .popup input {
              width: 330px;
              height: 39px;
              border-radius: 2px;
              border: solid 1.1px #dee2e6;
              margin-right: 9px;
              margin-bottom: 10px;
              padding-left: 10px;
          }
          
          .popup button.close {
              position: absolute;
              top: 15px;
              right: 15px;
              color: #adb5bd;
              background-color: #fff;
              font-size: 19px;
              border: none;
          }
          
          .popup button.cta {
              width: 369.1px;
              height: 43.9px;
              border-radius: 2px;
              background-color: #15aabf;
              border: none;
          }
          
          #search-area, #see-area {
              width: 530px;
              margin: auto;
          }
          
          .nav {
              width: 530px;
              margin: 30px auto;
              display: flex;
              align-items: center;
              justify-content: space-around;
          }
          
          .nav div {
              cursor: pointer;
          }
          
          .nav div.active {
              font-weight: 700;
          }
          
          .header {
              background-color: #15aabf;
              color: white;
              text-align: center;
              padding: 50px;
              font-size: 45px;
              font-weight: bold;
          }
          
          #product-container {
              grid-template-columns: 100px 50px 100px;
              grid-template-rows: 80px auto 80px;
              column-gap: 10px;
              row-gap: 15px;
          }
          
          .product-card {
              width: 300px;
              margin: auto;
              cursor: pointer;
          }
          
          .product-card .card-header {
              width: 300px;
          }
          
          .product-card .card-header img {
              width: 300px;
          }
          
          .product-card .card-body {
              margin-top: 15px;
          }
          
          .product-card .card-body .title {
              font-size: 15px;
              font-weight: normal;
              font-stretch: normal;
              font-style: normal;
              line-height: 1;
              letter-spacing: -0.75px;
              text-align: left;
              color: #343a40;
              margin-bottom: 10px;
          }
          
          .product-card .card-body .lprice {
              font-size: 15.8px;
              font-weight: normal;
              font-stretch: normal;
              font-style: normal;
              line-height: 1;
              letter-spacing: -0.79px;
              color: #000000;
              margin-bottom: 10px;
          }
          
          .product-card .card-body .lprice span {
              font-size: 21.4px;
              font-weight: 600;
              font-stretch: normal;
              font-style: normal;
              line-height: 1;
              letter-spacing: -0.43px;
              text-align: left;
              color: #E8344E;
          }
          
          .product-card .card-body .isgood {
              margin-top: 10px;
              padding: 10px 20px;
              color: white;
              border-radius: 2.6px;
              background-color: #ff8787;
              width: 42px;
          }
          
          .none {
              display: none;
          }
      2. src > main > resources > static 에 images 폴더 생성 후 이미지 저장

     

     

    함수 작성하기

    basic.js

    • 상품 검색 기능 - execSearch, addHTML 함수
      function execSearch() {
          /**
           * 검색어 input id: query
           * 검색결과 목록: #search-result-box
           * 검색결과 HTML 만드는 함수: addHTML
           */
      
          // 검색 결과 비우기
          $('#search-result-box').empty();
      
          // 1. 검색창의 입력값을 가져온다.
          let query = $('#query').val();
      
          // 2. 검색창 입력값을 검사하고, 입력하지 않았을 경우 focus.
          if (query == '') {
              alert('검색어를 입력해주세요');
              $('#query').focus();
          }
      
          // 3. GET /api/search?query=${query} 요청
          $.ajax({
              type: 'GET',
              url: `/api/search?query=${query}`,
              success: function (response) {
                  // 4. for 문마다 itemDto를 꺼내서 HTML 만들고 검색결과 목록에 붙이기!
                  for (let i = 0; i < response.length; i++) {
                      let itemDto = response[i];
                      let tempHtml = addHTML(itemDto);
                      $('#search-result-box').append(tempHtml);
                  }
              }
          })
      
      }
      
      function addHTML(itemDto) {
          /**
           * class="search-itemDto" 인 녀석에서
           * image, title, lprice, addProduct 활용하기
           * 참고) onclick='addProduct(${JSON.stringify(itemDto)})'
           */
          return `<div class="search-itemDto">
              <div class="search-itemDto-left">
                  <img src="${itemDto.image}" alt="">
              </div>
              <div class="search-itemDto-center">
                  <div>${itemDto.title}</div>
                  <div class="price">
                      ${numberWithCommas(itemDto.lprice)}
                      <span class="unit">원</span>
                  </div>
              </div>
              <div class="search-itemDto-right">
                  <img src="images/icon-save.png" alt="" onclick='addProduct(${JSON.stringify(itemDto)})'>
              </div>
          </div>`
      }
    • 관심 상품 등록 - addProduct 함수
      function addProduct(itemDto) {
          /**
           * modal 뜨게 하는 법: $('#container').addClass('active');
           * data를 ajax로 전달할 때는 두 가지가 매우 중요
           * 1. contentType: "application/json",
           * 2. data: JSON.stringify(itemDto),
           */
          // 1. POST /api/products 에 관심 상품 생성 요청
          $.ajax({
              type: 'POST',
              url: '/api/products',
              data: JSON.stringify(itemDto),
              contentType: 'application/json',
              success: function (response) {
                  // 2. 응답 함수에서 modal을 뜨게 하고, targetId 를 response.id 로 설정 (숙제로 myprice 설정하기 위함)
                  $('#container').addClass('active');
                  targetId = response.id;
              }
          })
      }
    • 관심 상품 조회 - showProduct, addProductItem 함수
      function showProduct() {
          /**
           * 관심상품 목록: #product-container
           * 검색결과 목록: #search-result-box
           * 관심상품 HTML 만드는 함수: addProductItem
           */
          // 1. GET /api/products 요청
          $.ajax({
              type: 'GET',
              url: '/api/products',
              success: function (response) {
                  // 2. 관심상품 목록, 검색결과 목록 비우기
                  $('#product-container').empty();
                  $('#search-result-box').empty();
                  // 3. for 문마다 관심 상품 HTML 만들어서 관심상품 목록에 붙이기!
                  for (let i = 0; i < response.length; i++) {
                      let product = response[i];
                      let tempHtml = addProductItem(product);
                      $('#product-container').append(tempHtml);
                  }
              }
          })
      }
      
      function addProductItem(product) {
          // link, image, title, lprice, myprice 변수 활용하기
          return `<div class="product-card" onclick="window.location.href='${product.link}'">
                  <div class="card-header">
                      <img src="${product.image}"
                           alt="">
                  </div>
                  <div class="card-body">
                      <div class="title">
                          ${product.title}
                      </div>
                      <div class="lprice">
                          <span>${numberWithCommas(product.lprice)}</span>원
                      </div>
                      <div class="isgood ${product.lprice > product.myprice ? 'none' : ''}">
                          최저가
                      </div>
                  </div>
              </div>`;
      }
    • 관심 가격 설정 - setMyprice 함수
      function setMyprice() {
          /**
           * 숙제! myprice 값 설정하기.
           * 1. id가 myprice 인 input 태그에서 값을 가져온다.
           * 2. 만약 값을 입력하지 않았으면 alert를 띄우고 중단한다.
           * 3. PUT /api/product/${targetId} 에 data를 전달한다.
           *    주의) contentType: "application/json",
           *         data: JSON.stringify({myprice: myprice}),
           *         빠뜨리지 말 것!
           * 4. 모달을 종료한다. $('#container').removeClass('active');
           * 5, 성공적으로 등록되었음을 알리는 alert를 띄운다.
           * 6. 창을 새로고침한다. window.location.reload();
           */
      
          // 1. id가 myprice 인 input 태그에서 값을 가져온다.
          let myprice = $('#myprice').val();
      
          // 2. 만약 값을 입력하지 않았으면 alert를 띄우고 중단한다.
          if (myprice == '') {
              alert("올바른 가격을 입력해주세요");
              return;
          }
          // 3. PUT /api/products/${targetId} 에 data를 전달한다.
          $.ajax({
              type: "PUT",
              url: `/api/products/${targetId}`,
              contentType: "application/json",
              data: JSON.stringify({myprice: myprice}),
              success: function (response) {
                  // 4. 모달을 종료한다. $('#container').removeClass('active');
                  $('#container').removeClass('active');
                  // 5, 성공적으로 등록되었음을 알리는 alert를 띄운다.
                  alert("성공적으로 등록되었습니다.");
                  // 6. 창을 새로고침한다. window.location.reload();
                  window.location.reload();
              }
          })
      
      
      }

    + Recent posts