장바구니 기능
: 상품 목록을 보여주고 담기 버튼 클릭 시 장바구니에 담기며, 담은 상품의 합계와 영수증을 보여주는 페이지
사용 언어
- HTML
- CSS
- Javascript
구조
- index.html - 전체 구조
- index.js - 기능 구현
- main.css - 전체 디자인
- store.json - 데이터 파일
코드
구현 계획
1. 레이아웃 구성하기 (html, css)
- 상품 카드 만들기
2. 상품 목록 데이터 끌어오기
- 데이터 갯수만큼 html 생성하기
3. 검색 기능
(1) 검색어 필터링
(2) 검색어가 없는 카드엔 hide 클래스 추가 / 있으면 제거
4. 상품 드래그 기능
5. 장바구니에 있는 상품 카드의 html 수정
- 수량 입력 칸 추가 (<input>)
7. 최종 가격 출력
8. 개인 정보 입력창
- 유효성 검사
9. 영수증 출력
- canvas 태그 사용
주요 기능
JSON 파일의 데이터 가져오기
- 데이터 개수만큼 html 카드 생성하기
$.get('store.json')
.then((data)=>{
products = data.products; // 원본 데이터 변수에 보관
// json 데이터 개수만큼 상품 카드 html 생성하기
data.products.forEach((a) => {
let html = `
<div class="container" id="dragMe" draggable="true" ondragstart="drag(event)">
<div class="item" data-id="${a.id}">
<div class="image">
<img src="${a.photo}" alt="" style="width: 200px; height: 150px;">
</div>
<div class="top">
<div class="title">${a.title}</div>
<div class="company">${a.brand}</div>
</div>
<div class="price">가격: ${a.price}</div>
<button data-id="${a.id}" class="cartBtn">Add to Cart</button>
</div>
</div> `;
$('.card').append(html)
});
검색어 기능
- include( ) 사용
document.getElementById('search').addEventListener('input', function() {
let search = document.getElementById('search').value;
for (let i = 0; i < products.length; i++) {
if (products[i]['title'].includes(search) || products[i]['brand'].includes(search)) {
document.getElementsByClassName('container')[i].classList.remove('hide')
} else {
document.getElementsByClassName('container')[i].classList.add('hide');
}
}
});
담기 버튼 누르면 장바구니에 담기
$(document).on('click', '.cartBtn', function(e) {
// 현재 클릭된 버튼이 'cartBtn' 클래스를 가지고 있는지 확인
if ($(e.target).hasClass('cartBtn')) {
// 지금 누른 버튼의 번호
let productId = e.target.dataset.id;
// 장바구니 'cart'에 지금 누른 버튼의 번호 == 현재 클릭된 상품의 ID가 같은 상품을 찾기
let productFind = cart.findIndex((a)=>{ return a.id == productId })
// 없으면 cart에 {새 상품}추가, 그 수량을 1로 설정
if (productFind == -1) {
let now = products.find((a)=> { return a.id == productId });
now.count = 1;
cart.push(now);
} else { // 있으면 수량만 증가
cart[productFind].count++;
}
// 담기 버튼 누를 때 마다 장바구니에 html 생성
$('.dropBox').html('');
cart.forEach((a, i)=>{
$('.dropBox').append(`
<div class="container" id="dragMe" draggable="true" ondragstart="drag(event)">
<div class="item" data-id="${a.id}">
<div class="image">
<img src="${a.photo}" alt="" style="width: 200px; height: 150px;">
</div>
<div class="top">
<div class="title">${a.title}</div>
<div class="company">${a.brand}</div>
</div>
<div class="price">가격: ${a.price}</div>
<button data-id="${a.id}" class="cartBtn">Add to Cart</button>
</div>
<input type="number" value="${a.count}" class="item-count">
</div>
`);
});
});
정렬 버튼
- 가격순 정렬, 2만원 이하 정렬
// -----------------가격순 정렬-----------------
document.getElementById('priceDown').addEventListener('click', function() {
// 1. 가격순 정렬
products.sort(function(a, b) {
return b.price - a.price
});
// 2. 카드 div의 내용을 삭제하기
document.querySelector('.card').innerHTML = '';
// 3. 새로 정렬한 데이터 갯수만큼 카드 생성하기
products.forEach((a, i) => {
var a = `
<div class="container" id="dragMe" draggable="true" ondragstart="drag(event)">
<div class="item" data-id="${a.id}">
<div class="image">
<img src="${a.photo}" alt="" style="width: 200px; height: 150px;">
</div>
<div class="top">
<div class="title">${a.title}</div>
<div class="company">${a.brand}</div>
</div>
<div class="price">가격: ${a.price}</div>
<button data-id="${a.id}" class="cartBtn">Add to Cart</button>
</div>
</div> `;
document.querySelector('.card').insertAdjacentHTML('beforeend', a);
});
});
// -----------------2만원 이하 정렬-----------------
document.getElementById('down-2').addEventListener('click', function() {
// 1. 가격이 2만원 이하 필터링
var newProduct = products.filter(function(a) {
return a.price <= 20000;
})
// 2. 카드 div의 내용을 삭제하기
document.querySelector('.card').innerHTML = '';
// 3. 새로 정렬한 데이터 갯수만큼 카드 생성하기
newProduct.forEach((a, i) => {
var a = `
<div class="container" id="dragMe" draggable="true" ondragstart="drag(event)">
<div class="item" data-id="${a.id}">
<div class="image">
<img src="${a.photo}" alt="" style="width: 200px; height: 150px;">
</div>
<div class="top">
<div class="title">${a.title}</div>
<div class="company">${a.brand}</div>
</div>
<div class="price">가격: ${a.price}</div>
<button data-id="${a.id}" class="cartBtn">Add to Cart</button>
</div>
</div> `;
document.querySelector('.card').insertAdjacentHTML('beforeend', a);
});
});
최종 가격
function priceCalc(){
let finalPrice = 0; // 가격을 저장할 변수
// 각 상품(수량 입력 필드)에 대해 가격과 수량을 곱하여 합산
$('.item-count').each(function() {
// 입력 필드의 부모 요소(container)를 찾은 후, 그 하위에 있는 "price" 클래스 요소의 텍스트를 가져옴(= 각 상품의 가격)
var priceText = $(this).parent().find('.price').text();
// "가격: " 뒤의 숫자만 추출해서 가격으로 사용
// 문자열을 공백으로 분할, 두 번째 요소(인덱스 1)를 실수로 변환
var price = parseFloat(priceText.split(' ')[1]);
// 수량 입력 필드 값(개수)을 정수로 변환
var count = parseInt($(this).val());
// 최종 가격 계산 (가격 * 개수)
finalPrice += price * count;
});
// 가격 3자리 마다 콤마(,) 찍는 함수
let formattedPrice = finalPrice.toLocaleString();
$('.final-price').html('합계: ' + formattedPrice + '원');
}
영수증 모달창
document.getElementById('send').addEventListener('click', function () {
// 기존 모달 닫기
document.querySelector('.modal1').classList.remove('show-modal');
let name = document.querySelectorAll('.m-input')[0].value;
let phone = document.querySelectorAll('.m-input')[1].value;
// 개인 정보 유효성 검사
// 이름
if (name == '') {
alert('이름을 입력하세요.');
e.preventDefault()
} else if (/[a-zA-Zㄱ-ㅎ0-9]/.test(name)) {
alert('이름은 한글 입력만 가능합니다.');
e.preventDefault()
}
if (name.length < 2 || name.length > 5) {
alert('이름 제한 길이를 넘었습니다.');
e.preventDefault()
}
// 전화번호
if (phone == '') {
alert('전화번호를 입력하세요.');
e.preventDefault()
} else if ((!/^(01[016789]{1})-[0-9]{4}-[0-9]{4}$/.test(phone)) && (!/^(01[016789]{1})[0-9]{4}[0-9]{4}$/.test(phone))) {
alert('전화번호 형식이 틀렸습니다.');
e.preventDefault()
}
// 영수증 모달 열기
document.getElementById('receiptModal').style.display = 'block';
// 영수증 캔버스 생성 및 내용 그리기
var canvas = document.createElement('canvas');
canvas.id = 'receiptCanvas';
canvas.width = 500;
canvas.height = 800;
var c = canvas.getContext('2d');
c.font = '18px dotum';
c.fillText('이름: ' + name, 170, 30);
c.fillText('전화번호: ' + phone, 170, 60);
// 구매 시간!!!!!!!!!!!!!!!!!!!!!!!
// 장바구니에 저장된 각 상품 정보 출력
let yPosition = 120; // 출력할 상품의 시작 위치
cart.forEach((product) => {
c.fillText('상품명: ' + product.title, 170, yPosition);
c.fillText('가격: ' + product.price, 170, yPosition + 30);
c.fillText('수량: ' + product.count, 170, yPosition + 60);
c.fillText('합계: ' + (product.price * product.count), 170, yPosition + 90);
yPosition += 135; // 다음 상품 정보의 출력 위치 조정
});
// 총 합계 계산
let totalPrice = cart.reduce((total, product) => total + (product.price * product.count), 0);
c.fillText('총 합계: ' + totalPrice, 170, yPosition + 10);
// 생성된 캔버스를 영수증 모달에 추가
var modalContent = document.querySelector('#receiptModal .modal-content');
modalContent.appendChild(canvas);
});
// 영수증 닫기 버튼
document.getElementById('closeReceipt').addEventListener('click', function () {
document.getElementById('receiptModal').style.display = 'none';
});
잘한 점
끝까지 포기하지 않고 완성한 것..............
아쉬운 점
지금의 내게는 너무 어려운 과제였어서 아쉬운 점이 많다.
vanilla js로만 구현하고 싶었는데 jQuery를 사용하지 않으면 도저히 다음으로 넘어갈 수 없는 부분들이 있었고,
어쩔 수 없이 둘을 혼용해서 사용하였다.
드래그 이벤트를 제대로 구현하지 못했다.
상품을 드래그하면 무조건 1번 상품으로만 생성되는 치명적인 오류가 있다.
최대한 수정해보려 했지만 내 지식의 한계인지 도무지 원인을 알아낼 수 없어서 상품을 드래그해서 장바구니에 추가하는 기능은 차후에 수정하려고 한다.
깃허브
https://github.com/kwonboryong/Toy_Projects/tree/main/Cart
출처
코딩애플