프로그래머스 커피 주문 페이지 만들기 - 개발 (장바구니 페이지)

Z6su3·2022년 5월 9일
0

장바구니 페이지의 구현 순서는 다음과같습니다.

  • localStorage 데이터 렌더링
  • 장바구니의 총액 출력
  • 장바구니에 담는 상품의 특징
    • 여러종류 담기 가능
    • 여러 페이지에서 선택하는 다양한 상품은 장바구니에 존재해야 한다.
  • alert 출력 및 라우팅
    • 주문하기 클릭 시 주문하기완료. localStorage값 전부 삭제
    • 장바구니가 비어있으면, 비어있음을 알림

장바구니 페이지의 기본적 마크업 구조를 index.html에서 가져오고, 렌더링을 진행한 다음 장바구니에 상품을 담을 때 필요한 제한 요소를 해결한 뒤 alert로 마무리합니다.

localStorage 데이터 렌더링

localStorage에서 데이터를 꺼내오는 getLocalStorage를 생성 후 사용해줍니다. 이후 장바구니에 상품목록을 렌더링합니다. 꺼내온 데이터는, CartPage 컴포넌트를 생성하기 전, App에서 온전한 데이터로 변환하여 진행했습니다.

LocalStorage.js

export const getLocalStorage = (query) => {
  return JSON.parse(localStorage.getItem(query));
};
...

App.js

...
import { getLocalStorage } from "./lib/LocalStorage.js";

export default function App($app) {
  ...
  this.init = async () => {
    ...

    switch (routeComponent.path) {
      case "/web/":
        componentData = await request();
        break;
      case "/web/products":
        componentData = await request(history.state);
        break;
      case "/web/cart":
        const cartData = getLocalStorage("products_cart");
        await cartData.map(async (cart) => {
          const productInfo = await request({ productId: cart.productId });
          const optionInfo = productInfo.productOptions.find(
            (option) => option.id === cart.optionId
          );
          componentData.push({
            product: productInfo,
            option: optionInfo,
            quantity: cart.quantity,
          });
        });
        break;
    }

    ...
  };
  this.init();
	...
}

데이터는 productId, optionId, quantity가 주어지므로, 각 id에 매칭되는 객체값과 수량을 componentData에 실어 넘겨줍니다. 장바구니가 비어있다면 해당값은 빈 배열 [] 로 넘어가게됩니다.

CartPage.js

export default function CartPage({ $app, initialState }) {
  this.state = initialState;
  this.$target = document.createElement("div");
  this.$target.className = "CartPage";
  $app.appendChild(this.$target);

  this.render = () => {
    this.$target.innerHTML = `
      <h1>장바구니</h1>
      <div class="Cart">
        <ul>
          ${this.state
            .map((cart) => {
              const cartPrice = cart.product.price + cart.option.price;
              const totalPrice = cartPrice * cart.quantity;
              return `
                <li class="Cart__item">
                  <img src="${cart.product.imageUrl}" />
                  <div class="Cart__itemDesription">
                    <div>
                      ${cart.product.name}
                      ${cart.option.name}
                      ${cart.quantity}개
                    </div>
                    <div>${totalPrice.toLocaleString()}원</div>
                  </div>
                </li>
              `;
            })
            .join("")}
        </ul>
        <div class="Cart__totalPrice">총 상품가격 100,000원</div>
        <button class="OrderButton">주문하기</button>
      </div>
    `;
  };
}

장바구니 페이지에 index.html의 마크업을 기반으로 목록을 렌더링합니다.

장바구니의 총액 출력

장바구니의 총액을 담기 위한 변수를 this.totalPrice로 생성했고, 총 상품가격 위치에 렌더링 시켜줍니다.

CartPage.js

export default function CartPage({ $app, initialState }) {
	...
  this.totalPrice = 0;
	...

  this.render = () => {
    this.$target.innerHTML = `
      <h1>장바구니</h1>
      <div class="Cart">
        <ul>
          ...
        </ul>
        <div class="Cart__totalPrice">총 상품가격 ${this.totalPrice.toLocaleString()}원</div>
        <button class="OrderButton">주문하기</button>
      </div>
    `;

장바구니 상품 특징 적용

장바구니 상품은 여러종류를 담을 수 있어야하고, 어떤 페이지에서든 상품을 주문하면 장바구니에 추가되어야 합니다.

해당 문제를 해결하기 위해서, ProductDetailPage에서 주문을 할 때, localStorage에서 데이터를 확인하고 기존 데이터에 추가한 뒤 다시 저장해줍니다.

여기서 한가지 발생하는 문제점은, 같은 옵션을 가진 상품을 재주문할 때, 어떻게 처리할 지였는데, 요구사항에는 적혀있지 않지만 발생할 수 있다 생각하여 고려하고 개발했습니다.

ProductDetailPage.js

import { getLocalStorage, setLocalStorage } from "../lib/LocalStorage.js";

export default function ProductDetailPage({ $app, initialState, onClick }) {
  ...

  this.onClick = onClick;

  const buttonClickEvent = () => {
    const $inputItems = [].slice.call(document.querySelectorAll("input"));
    if ($inputItems) {
			//LocalStorage의 값을 가져옵니다.
      let cartData = getLocalStorage("products_cart");
			//LocalStorage 데이터 임시 저장 변수 생성, 없으면 빈 배열
      cartData = cartData ? cartData : [];

      $inputItems.map((inputItem) => {
        const { index } = inputItem.dataset;
        const quantity = parseInt(inputItem.value);
				//새로 추가되는 데이터 임시 저장
        const inputData = {
          productId: this.state.id,
          optionId: this.state.productOptions[index].id,
          quantity: quantity,
        };
				//LocalStorage에 값이 존재하고 있으면 데이터를 cartData에 추가한다
				//단, 이미 존재하고 있는 옵션의 상품이면 해당 수량을 증가시킨다.
        if (cartData.length !== 0) {
          const searchIndex = cartData.findIndex(
            (cart) =>
              cart.productId == inputData.productId &&
              cart.optionId == inputData.optionId
          );
          if (searchIndex != -1) {
            cartData[searchIndex].quantity += inputData.quantity;
            return;
          }
        }
        cartData.push(inputData);
      });
			//LocalStorage에 다시 저장
      setLocalStorage(cartData);
    }
    this.onClick(null, null, "/web/cart");
  };
}

이후 App.js에서 기존과 같이 localStorag에서 데이터를 요청 후 넘겨주고, CartData에서 렌더링합니다.

alert 출력 및 라우팅

CartPage.js에서 넘겨받은 componentData, 즉 this.state가 존재하지 않거나, 길이가 0인 경우 장바구니가 비어있음을 표시하고 상품목록 페이지로 넘어가는 경우

그리고 주문하기 버튼 클릭 시도 같은 경우로 alert출력 후, localStorage데이터를 삭제, 라우팅을 진행해주면 됩니다.

단, 장바구니 페이지가 렌더링 되는 시점에, this.state에 대한 유효성검사를 그냥 진행하는 경우, 별 다른 행위 없이 바로 상품목록 페이지로 넘어가게 됩니다.

해당 부정적 경험을 막기 위해, 장바구니 페이지요소가 충분히 렌더링 되고 난 후 유효성검사를 진행할 수 있도록 setTiemout을 활용하였습니다.

CartPage.js

import { deleteLocalStorage } from "../lib/LocalStorage.js";

export default function CartPage({ $app, initialState, onClick }) {
  ...
  this.onClick = onClick;

  this.render = () => {
    ...

    setTimeout(() => {
      if (!this.state || this.state.length === 0) {
        alert("장바구니가 비었습니다.");
        this.onClick(null, null, "/web/");
      }
    });
  };

  this.$target.addEventListener("click", (e) => {
    const $buttonItem = e.target.closest(".OrderButton");
    if ($buttonItem) {
      alert("주문 되었습니다.");
      deleteLocalStorage("products_cart");
      this.onClick(null, null, "/web/");
    }
  });
}
profile
기억은 기록을 이길수 없다

0개의 댓글