Spring
[Spring] 나만의 셀렉샵 프로젝트 - 클라이언트(HTML, Javascript, jQuery, CSS)
jelliclesu
2024. 7. 30. 19:08
프로젝트 설계하기
필요 기능
- 키워드로 상품 검색하고 그 결과를 목록으로 보여주기
- 관심 상품 등록하기
- 관심 상품 조회하기
- 관심 상품에 원하는 가격 등록하고, 그 가격보다 낮은 경우 표시하기
HTML, 이미지 파일 준비
- 파일 분리
✔️ HTML 파일이 CSS 와 Javascpript 때문에 지나치게 길어지는 것을 방지하고 가독성을 높이기 위함
✔️ .css 와 .js 로 끝나는 파일 생성 후 link 와 script 태그로 각 파일을 불러오면 index.html 파일에 모두 작성한 것과 동일하게 작동
- 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; }
- index.html
- src > main > resources > static 에 images 폴더 생성 후 이미지 저장
- src > main > resources > static 에 파일 생성
함수 작성하기
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(); } }) }