자판기 음료수 목록 만들기
<ul class="cont-lists">
<!-- 음료수 목록 -->
</ul>
// 음료수 목록 만들기
beverages.forEach((item) => {
const beverageItem = document.createElement("li");
if (!item["quantity"]) beverageItem.classList.add("sold-out");
// 아이디 숨겨서
const beverageId = document.createElement("input");
beverageId.setAttribute("type", "hidden");
beverageId.setAttribute("id", item["id"]);
beverageId.setAttribute("class", "beverageId");
beverageItem.appendChild(beverageId);
// 수량 숨겨서
const beverageQuantity = document.createElement("input");
beverageQuantity.setAttribute("type", "hidden");
beverageQuantity.setAttribute("class", "quantity");
beverageQuantity.setAttribute("id", item["id"] + item["name"]);
beverageQuantity.setAttribute("value", item["quantity"]);
beverageItem.appendChild(beverageQuantity);
// 버튼 만들고 그 안에 이미지, 이름, 가격 넣기
const beverageBtn = document.createElement("button");
beverageBtn.classList.add("btn-item");
// 이미지
const beverageImg = document.createElement("img");
beverageImg.setAttribute("src", item["source"]);
beverageImg.classList.add("img-item");
beverageBtn.appendChild(beverageImg);
// 이름
const beverageName = document.createElement("strong");
beverageName.textContent = item["name"];
beverageName.classList.add("name-item");
beverageBtn.appendChild(beverageName);
// 가격
const beveragePrice = document.createElement("span");
beveragePrice.textContent = item["price"] + "원";
beveragePrice.classList.add("price-item");
beverageBtn.appendChild(beveragePrice);
beverageItem.appendChild(beverageBtn);
beveragesList.appendChild(beverageItem);
});
- beverages.js에서 음료수 데이터를 불러와
ul.cont-list
의 자식 요소로 렌더링되도록 작성했다.
자판기 음료수 버튼
const beverageItemEL = beveragesList.querySelectorAll("li");
beverageItemEL.forEach((el) => {
el.addEventListener("click", (e) => {
//...
// 이미 리스트에 있으면 수량만 올려주기
const beverageCartItem = document.querySelectorAll(".cart-item");
let change = false;
if (beverageCartItem.length) {
beverageCartItem.forEach((cartItem) => {
// 자판기의 음료id와 카트의 음료id를 가져와서 있는지 확인하기
const beverageId = el.querySelector(".beverageId").getAttribute("id");
const beverageIdInCart = cartItem
.querySelector(".beverageIdInCart").value;
if (beverageId === beverageIdInCart) {
let cartQuantity = cartItem.querySelector(".cart-quantity");
console.log(cartQuantity.textContent);
cartQuantity.textContent = parseInt(cartQuantity.textContent, 10) + 1;
change = true;
}
});
}
// ...
}
}
- 음료수 목록에 있는 음료수 id와 카트 목록에 있는 음료수 id를 카트 아이템들을 루프 돌면서 같은 id가 있으면 카트 리스트에 추가하지 않고 수량만 올릴 수 있도록 작성했다.
- 카트 리스트의 음료수 id:
const beverageIdInCart = cartItem.querySelector(".beverageIdInCart").getAttribute("id");
- 자판기 리스트의 음료수 id:
const beverageIdInCart = cartItem.querySelector(".beverageIdInCart").value;
- 카트 리스트의 음료수 id:
<ul class="list-cart">
<!-- 음료수 장바구니 리스트 -->
</ul>
if (!change) {
const cartItem = document.createElement("li");
cartItem.classList.add("cart-item");
// 음료수아이디 넣기
const beverageId = el.querySelector(".beverageId").getAttribute("id");
const beverageIdInCart = document.createElement("input");
beverageIdInCart.setAttribute("type", "hidden");
beverageIdInCart.setAttribute("value", beverageId);
beverageIdInCart.setAttribute("class", "beverageIdInCart");
cartItem.appendChild(beverageIdInCart);
// ...
listCartEl.append(cartItem);
updateCartListItem();
}
!change
는 id가 같은 것이 없다면 새로운 음료수가 카트에 추가된 것이므로 해당 코드처럼ul.list-cart
의 자식 요소로 렌더링되도록 작성했다.updateCartListItem()
함수는 새로운li
자식 요소가 생기면 해당 요소에 이벤트를 설정하는 로직을 작성했다.
카트 리스트 버튼
// 카트 리스트 수량 내리고 자판기 수량 올리고 카트 리스트 아이템의 수량 0이 되면 제거
function updateCartListItem() {
const cartListItem = listCartEl.querySelectorAll("li");
// 카트 아이템이 들어오면 가장 최신 카트 아이템에 이벤트 리스너 설정
const lastedCartItem = cartListItem[cartListItem.length - 1];
const beverageIdInCart =
lastedCartItem.querySelector(".beverageIdInCart").value;
const beverageNameInCart =
lastedCartItem.querySelector(".cart-name").textContent;
const beravegeQuantityInCart = lastedCartItem.querySelector(".cart-quantity");
// 벤딩 아이템의 수량 input을 찾아서 value값 증가 및 카트 아이템의 수량 감소
lastedCartItem.addEventListener("click", () => {
const vendingItemQuantity = document.querySelector(
"#" + beverageIdInCart + beverageNameInCart
);
vendingItemQuantity.value = parseInt(vendingItemQuantity.value, 10) + 1;
beravegeQuantityInCart.textContent =
parseInt(beravegeQuantityInCart.textContent, 10) - 1;
// 만약 카트 아이템의 수량이 0이 되면 카트 리스트에서 제거
if (beravegeQuantityInCart.textContent === "0") {
listCartEl.removeChild(lastedCartItem);
}
if (vendingItemQuantity.value > 0) {
vendingItemQuantity.parentNode.classList.remove("sold-out");
}
});
}
- 요구사항에서 버튼 누르면 카트 리스트에 아이템 1개씩 추가되는 것은 있었지만 해당 요구사항은 없어서 추가적으로 만들어 봤다.
const lastedCartItem = cartListItem[cartListItem.length - 1];
를 통해서 가장 최근에 들어온li
요소에 이벤트 설정이 가도록 작성했다.- 이렇게 하지 않고 그냥
forEach
문을 사용하면 이전의li
요소의 이벤트가 중복으로 계속 생긴다.
- 이렇게 하지 않고 그냥
if (beravegeQuantityInCart.textContent === "0") {}
- 카트 아이템의 수량이 0이 되면 카트 리스트에서 제거되도록 작성했다.
if (vendingItemQuantity.value > 0) {}
- 자판기 음료수의 수량이 0이 되면 품절 스티커가 붙는데 카트에서 수량 하나 줄이면 1개가 되므로 품절 스티커 없애도록 작성했다.
획득 버튼 구현
// 총 가격 비교하고 아니면 그냥 리턴
let totalPrice = 0; // 총 가격 가져오기
cartItems.forEach((el) => {
const price = el.querySelector(".beveragePriceInCart").value;
const quantity = el.querySelector(".cart-quantity").textContent;
// 총가격
totalPrice =
parseInt(totalPrice, 10) + parseInt(price, 10) * parseInt(quantity, 10);
});
if (myBalance < totalPrice) return alert("소지금이 부족합니다");
// 잔액에서 총 구매가격 빼기
txtBalanceEl.textContent = toKRW(myBalance - totalPrice) + " 원";
- 획득 하기 전에 총 가격을 구하고 그 가격이 잔액보다 크다면 그냥 경고만하도록 작성하고
- 아니라면, 잔액에서 총 구매 가격을 빼도록 작성했다.
// 획득한 음료가 만약 목록에 있으면 수량만 올리고 아니면 리스트에 추가
cartItems.forEach((el) => {
const beverageId = el.querySelector(".beverageIdInCart").value;
const beverageName = el.querySelector(".cart-name").textContent;
const beverageSource = el.querySelector("img").src;
const beveragePrice = el.querySelector(".beveragePriceInCart").value;
const beverageQuantityInCart =
el.querySelector(".cart-quantity").textContent;
// myBeveragesList 루프 돌면서 같은 아이디 있으면 카트에 있는 수량만 증가
let changeQuantity = false;
for (let i = 0; i < myBeveragesList.length; i++) {
if (
myBeveragesList[i]["beverageId"] === beverageId
) {
myBeveragesList[i]["beverageQuantity"] =
parseInt(myBeveragesList[i]["beverageQuantity"], 10) +
parseInt(beverageQuantityInCart, 10);
changeQuantity = true;
}
}
// myBeverageList에서 없다면 추가
if (!changeQuantity) {
myBeveragesList.push({
beverageId,
beverageName,
beverageSource,
beveragePrice: parseInt(beveragePrice, 10),
beverageQuantity: parseInt(beverageQuantityInCart, 10),
});
}
}
- 카트 아이템이 가지고 있는 정보를 변수에 할당하여 만약 획득한 음료 리스트에 같은 id가 있다면 수량만 구매하려는 음료수의 수량만 증가하도록 작성했다.
- 그리고 만약 같은 id가 없다면 새로운 음료수를 산 것이기 때문에 myBeverageList에 추가했다.
획득한 음료 리스트 렌더링
<ul class="cont-myBeverageList"></ul>
// 내가 획득한 음료
const myBeveragesList = [];
// 획득한 음료 리스트 렌더링
function renderMyBeverageList() {
// 음료 리스트 제거
while (contMyBeverageListEl.hasChildNodes()) {
contMyBeverageListEl.removeChild(contMyBeverageListEl.firstChild);
}
// 다시 리렌더링
let myTotalPrice = 0;
myBeveragesList.forEach((item) => {
const myBeverageLi = document.createElement("li");
myBeverageLi.classList.add("item-myBeverage");
// ...
contMyBeverageListEl.appendChild(myBeverageLi);
// 내가 구매한 총가격 구하기
myTotalPrice =
myTotalPrice + item["beverageQuantity"] * item["beveragePrice"];
});
txtTotalPriceEl.textContent = toKRW(myTotalPrice);
}
- 같은 id를 찾으면서 음료수 id가 같으면 수량만 올리도록 할 수도 있었지만 이번에는 다른 방식으로
ul.cont-myBeverageList
에 렌더링해봤다. while (contMyBeverageListEl.hasChildNodes()) {}
ul.cont-myBeverageList
에 있는 자식 노드들을 모두 먼저 제거했다.
- 위에서 업데이트된
myBeveragesList
를forEach
메서드를 사용하여 다시 렌더링되도록 작성했다.
소지금 입력하기
// 소지금 입력하기
function inputMyOwnMoney() {
let myMoney = prompt("소지금을 입력해주세요!");
while (true) {
// 문자가 들어있을 경우
if (isNaN(myMoney) && myMoney !== null) {
alert("숫자를 입력해주세요.");
myMoney = prompt("소지금을 입력해주세요!");
continue;
}
// 취소를 눌렀을 경우
if (myMoney === null) return (myMoney = txtMyMoneyEl.textContent);
break;
}
txtMyMoneyEl.textContent = toKRW(myMoney) + " 원";
}
txtMyMoneyEl.addEventListener("click", inputMyOwnMoney);
- 소지금을 입력했을 때 만약 문자가 섞여있다면 다시 입력하도록 설정
- 취소를 눌렀을 경우에는 가격 그대로 유지한다.
- 그리고
toKRW()
함수를 만들어서 utils.js에서 따로 관리할 수 있도록 작성했다.toKRW()
는 원화 콤마찍는 함수이다.
입금하기
// 입금하기
function deposit() {
// 소지금, 잔액
const myMoney = toNum(txtMyMoneyEl.textContent);
const myBalance = toNum(txtBalanceEl.textContent);
// 입금액을 입력하지 않은 경우
if (inpCreditEl.value.length === 0) return alert("입금액을 입력해주세요.");
// 입금액이 소지금보다 큰 경우
if (parseInt(inpCreditEl.value, 10) > myMoney) {
const cf = confirm(
"소지금이 부족합니다. 소지금을 입력 및 수정하시겠습니까?"
);
if (cf) {
inputMyOwnMoney(); // 소지금 입력하기
}
return (inpCreditEl.value = ""); // 다시 빈 칸
}
txtBalanceEl.textContent =
toKRW(myBalance + parseInt(inpCreditEl.value, 10)) + " 원";
txtMyMoneyEl.textContent =
toKRW(myMoney - parseInt(inpCreditEl.value, 10)) + " 원";
inpCreditEl.value = "";
}
btnCreditEl.addEventListener("click", deposit);
inpCreditEl.addEventListener("keyup", (e) => {
if (e.keyCode === 13) deposit();
});
- 입금하기 경우, input 요소에 아무것도 적지 않았거나 소지금보다 많으면 소지금 입력하겠냐는 알림을 통해
- 확인 누르면 소지금 입력할 수 있도록 작성했다.
- 취소 누르면 입금액을 빈 칸만 만들고 종료한다.
- 그리고 클릭이나 input 요소에 focus 상태에서 enter키를 누르면 해당 이벤트가 발생하도록 작성했다.
거스름돈 반환
btnBalanceEL.addEventListener("click", () => {
const myBalance = toNum(txtBalanceEl.textContent);
// 0이면 아래 더 실행안되게
if (myBalance === 0) return;
const myMoney = toNum(txtMyMoneyEl.textContent);
txtBalanceEl.textContent = "0 원";
txtMyMoneyEl.textContent = toKRW(myBalance + myMoney) + " 원";
});
- 거스름돈 반환하면 소지금에 추가되도록 작성했다.
태그 이름 | 설명 |
---|---|
Feat | 새로운 기능을 추가할 경우 |
Fix | 버그를 고친 경우 |
Design | CSS 등 사용자 UI 디자인 변경 |
Docs | 문서를 수정한 경우 |
- 판매할 음료에 대한 데이터는 따로 분리되어 있어야 합니다. (혹은 API로 받아야 합니다.)
- 돈의 입금과 음료의 선택 시점은 자유롭지만 돈이 모자라면 음료가 나와서는 안됩니다.
- 거스름돈이 나와야 합니다.
- 버튼을 누르면 상품이 1개씩 추가됩니다. (일반적인 자판기와 동일)