본문 바로가기

Javascript/활용팁&실전예제

장바구니 수량 변경 및 자동 합계 구하기

반응형

----------------------------------------------------------------------------------

2021.05.28 업데이트

소스에 체크박스 체크에 따라 값이 자동 계산되도록 개선

업/다운 화살표 아이콘을 삭제해도 기능 동작에 문제가 없도록 개선

----------------------------------------------------------------------------------

 

쇼핑몰 장바구니를 관리하는 프론트엔드 화면을 제작해보겠습니다.

 

쇼핑몰이 아니어도 견적서나, 금액 합계를 구하는데 적용할수 있는 기능으로 활용할 수 있습니다.

 

 

요청하시는 분이 있어 완성된 풀소스를 올려드립니다. 다운로드 받아 압축풀어서 html 파일을 열면 동작되는걸 확인해볼 수 있습니다.

 

basket.zip
0.00MB

 

기능 구현은 객체리터럴로 합니다.

 

먼저 HTML 페이지 구성을 살펴보겠습니다.

내용은 길지만 장바구니 상품목록이기 때문에 반복되는 내용은 생략했습니다.

가장 중요한 부분은 "장바구니 수량 변경" 부분입니다. 입력 필드에 표시되는 갯수를 직접 변경하거나, 화살표(업/다운)로 수량을 증가/감소하는 경우 자동으로 상품 합계와 수량 합계가 반영되어 보이도록 하는 것이 목표입니다.

 

<form name="orderform" id="orderform" method="post" class="orderform" action="/Order">
    <div class="basket" id="basket">
        <!-- "장바구니 헤더" -->
        <div class="row head">
            <div class="check">선택</div>
            <div class="img">이미지</div>
            <div class="pname">상품명</div>
            <div class="basketprice">가격</div>
            <div class="num">수량</div>
            <div class="sum">합계</div>
            <div class="basketcmd">삭제</div>
        </div>
        <!-- "장바구니 상품 목록" -->
        <div class="row data">
            <div class="check"><input type="checkbox" name="buy" value="260" checked="">&nbsp;</div>
            <div class="img"><img src="./img/basket1.jpg" width="60"></div>
            <div class="pname">
                <span>찜마마(XJ-92214/3)</span>
            </div>
            <div class="basketprice"><input type="hidden" name="p_price" id="p_price1" class="p_price" value="20000">20,000원</div>
            <div class="num">
                <!-- "장바구니 수량 변경" -->
                <div class="updown">
                    <input type="text" name="p_num1" id="p_num1" size="2" maxlength="4" class="p_num" value="2">
                    <span><i class="fas fa-arrow-alt-circle-up up"></i></span>
                    <span><i class="fas fa-arrow-alt-circle-down down"></i></span>
                </div>
            </div>
            <!-- "장바구니 상품 합계" -->
            <div class="sum">40,000원</div>
            <div class="basketcmd"><a href="#" class="abutton">삭제</a></div>
        </div>
    </div>
    <!-- "장바구니 기능 버튼" -->
    <div class="right-align basketrowcmd">
        <a href="#" class="abutton">선택상품삭제</a>
        <a href="#" class="abutton">장바구니비우기</a>
    </div>
    <!-- "장바구니 전체 합계 정보" -->
    <div class="bigtext right-align sumcount" id="sum_p_num">상품갯수: 4개</div>
    <div class="bigtext right-align box blue summoney" id="sum_p_price">합계금액: 74,200원</div>

    <div id="goorder" class="">
        <div class="clear"></div>
        <div class="buttongroup center-align cmd">
            <a href="#">선택한 상품 주문</a>
        </div>
    </div>
</form>

 

 

장바구니가 예쁘게 보이도록 CSS로 레이아웃을 만듭니다.

눈여겨 봐두어야 하는 것이 있는데, 테이블 구조처럼 보이지만 HTML 소스를 보면 <div></div>와 <span></span> 태그로 제작된 것입니다.

또는 리스트(<ul></ul>)로 만들 수도 있습니다.

<table></table> 태그를 사용하지 않는 것은 반응형 레이아웃을 위해서입니다.

기본적으로 테이블 태그는 반응형 레이아웃 및 모바일 화면에 적용이 쉽지 않기 때문에 반응형 레이아웃을 구현하려면 테이블 태그는 사용해서는 안됩니다.

 

/* 레이아웃 외곽 너비 400px 제한*/
.wrap{
    max-width480px;
    margin0 auto/* 화면 가운데로 */
    background-color#fff;
    height100%;
    padding20px;
    box-sizingborder-box;

}
.reviewform textarea{
    width100%;
    padding10px;
    box-sizingborder-box;
}
.rating .rate_radio {
    positionrelative;
    displayinline-block;
    z-index20;
    opacity0.001;
    width60px;
    height60px;
    background-color#fff;
    cursorpointer;
    vertical-aligntop;
    displaynone;
}
.rating .rate_radio + label {
    positionrelative;
    displayinline-block;
    margin-left-4px;
    z-index10;
    width60px;
    height60px;
    background-imageurl('./img/starrate.png');
    background-repeatno-repeat;
    background-size60px 60px;
    cursorpointer;
    background-color#f0f0f0;
}
.rating .rate_radio:checked + label {
    background-color#ff8;
}

.cmd{
    margin-top20px;
    text-alignright;
}
.cmd input[type="button"]{
    padding10px 20px;
    border1px solid #e8e8e8;
    background-color#fff;
    background-color:#000;  
    color#fff;
}

.warning_msg {
    displaynone;
    positionrelative;
    text-aligncenter;
    background#ffffff;
    line-height26px;
    width100%;
    colorred;
    padding10px;
    box-sizingborder-box;
    border1px solid #e0e0e0;
}

 

 

기본적인 장바구니 형태는 다 갖췄으므로 이제 자바스크립트로 기능 버튼들과 수량 변경에 반응하는 이벤트 리스너들을 추가합니다.

실제 동작 기능은 객체리터럴로 basket 객체에 메서드를 만들어서 구현을 하고, 여기서는 구현된 메서드가 있다고 가정하고, 이벤트 리스너를 추가합니다.

 

//이벤트 리스너 등록
document.addEventListener('DOMContentLoaded'function(){
    // "선택 상품 삭제" 버튼 클릭
    document.querySelector('.basketrowcmd a:first-child').addEventListener('click'function(){
        basket.delCheckedItem();
    });
    // "장바구니 비우기" 버튼 클릭
    document.querySelector('.basketrowcmd a:nth-child(2)').addEventListener('click'function(){
        basket.delAllItem();
    });
    // 장바구니 행 "삭제" 버튼 클릭
    document.querySelectorAll('.basketcmd a').forEach(
        function(item){
            item.addEventListener('click'function(){
                basket.delItem();
            });
        }
    );   
    // 수량변경 - 이벤트 델리게이션으로 이벤트 종류 구분해 처리
    document.querySelectorAll('.updown').forEach(
        function(itemidx){
            //수량 입력 필드 값 변경
            item.querySelector('input').addEventListener('keyup'function(){
                basket.changePNum(idx+1);
            });
            //수량 증가 화살표 클릭
            item.children[1].addEventListener('click'function(){
                basket.changePNum(idx+1);
            });
            //수량 감소 화살표 클릭
            item.children[2].addEventListener('click'function(){
                basket.changePNum(idx+1);
            });
        }
    );
    //앵커 # 대체해 스크롤 탑 차단
    document.querySelectorAll('a[href="#"]').forEach(function(item){
        item.setAttribute('href','javascript:void(0)');
    });
});

 

 

이제 basket 객체 리터럴을 만듭니다.

중요한 부분은 reCalc() 와 updateUI() 메서드입니다.

이벤트 리스너에서 클릭 이벤트가 발생해 수량이 변경되면, 합계를 다시 구하는 reCalc()를 호출하고, 합계 계산이 완료되면 updateUI() 메서드를 호출해 계산해서 저장한 합계 값을 화면에 반영합니다.

 

예제에서는 자동 합계만을 구하지만, 실제 쇼핑몰 장바구니 기능에는 더 많은 화면 업데이트 요소들이 있기 때문에 화면 갱신하는 부분만 별도의 메서드로 분리해 관리함으로써 화면 갱신을 일관되게 관리할 수 있기 때문입니다.

 

let basket = {
    totalCount: 0//전체 갯수 변수
    totalPrice: 0//전체 합계액 변수
    //체크한 장바구니 상품 비우기
    delCheckedItem: function(){
        document.querySelectorAll("input[name=buy]:checked").forEach(function (item) {
            item.parentElement.parentElement.parentElement.remove();
        });
        //AJAX 서버 업데이트 전송
    
        //전송 처리 결과가 성공이면
        this.reCalc();
        this.updateUI();
    },
    //장바구니 전체 비우기
    delAllItem: function(){
        document.querySelectorAll('.row.data').forEach(function (item) {
            item.remove();
          });
          //AJAX 서버 업데이트 전송
        
          //전송 처리 결과가 성공이면
          this.totalCount = 0;
          this.totalPrice = 0;
          this.reCalc();
          this.updateUI();
    },
    //재계산
    reCalc: function(){
        this.totalCount = 0;
        this.totalPrice = 0;
        document.querySelectorAll(".p_num").forEach(function (item) {
            var count = parseInt(item.getAttribute('value'));9999
            this.totalCount += count;
            var price = item.parentElement.parentElement.previousElementSibling.firstElementChild.getAttribute('value');
            this.totalPrice += count * price;
        }, this); // forEach 2번째 파라메터로 객체를 넘겨서 this 가 객체리터럴을 가리키도록 함. - thisArg
    },
    //화면 업데이트
    updateUI: function () {
        document.querySelector('#sum_p_num').textContent = '상품갯수: ' + this.totalCount.formatNumber() + '개';
        document.querySelector('#sum_p_price').textContent = '합계금액: ' + this.totalPrice.formatNumber() + '원';
    },
    //개별 수량 변경
    changePNum: function (pos) {
        var item = document.querySelector('input[name=p_num'+pos+']');
        var p_num = parseInt(item.getAttribute('value'));
        var newval = event.target.classList.contains('up') ? p_num+1 : event.target.classList.contains('down') ? 
p_num-1 : event.target.value;
        
        if (parseInt(newval) < 1 || parseInt(newval) > 99) { return false; }

        item.setAttribute('value'newval);
        item.value = newval;

        var price = item.parentElement.parentElement.previousElementSibling.firstElementChild.getAttribute('value');
        item.parentElement.parentElement.nextElementSibling.textContent = (newval * price).formatNumber()+"원";
        //AJAX 업데이트 전송

        //전송 처리 결과가 성공이면    
        this.reCalc();
        this.updateUI();
    },
    //상품 삭제
    delItem: function () {
        event.target.parentElement.parentElement.parentElement.remove();
    }
}

 

 

화면 출력하는 메서드중에 formatNumber() 메서드는 숫자 3자리 단위로 콤마를 찍어주는 함수입니다.

아래처럼 프로토타입으로 메서드를 만들어 전역으로 사용합니다.

자주 사용하는 기능이므로 라이브러리로 따로 분리해 공통 메서드로 사용하면 유용합니다.

 

// 숫자 3자리 콤마찍기
Number.prototype.formatNumber = function(){
    if(this==0return 0;
    let regex = /(^[+-]?\d+)(\d)/;
    let nstr = (this + '');
    while (regex.test(nstr)) nstr = nstr.replace(regex'$1' + ',' + '$2');
    return nstr;
};

 

반응형
  • 짱짱 2021.02.17 00:59 댓글주소 수정/삭제 댓글쓰기

    정말 감사합니다 도움많이됬습니다!!
    혹시 풀소스있나요?

  • 질문이요 2021.04.23 15:22 댓글주소 수정/삭제 댓글쓰기

    검은 화면마다 다른 클래스 입력해야 하는건가요? 장바구니 수량 수정할 때마ㅏ다 가격 수정되게 하고 싶은데 잘 모르겠어요

    • 검은 화면마다라는게 어떤걸 말하는 건지 모르겠습니다.
      상단에 풀소스 압축파일 링크 있으니까 다운로드 받아서 웹브라우저에 띄우시고 확인해보세요.
      동작구조가 이해되면 그다음에 소스 하나씩 봐가면서 조금씩 손봐보시면 이해가 조금 더 쉬울겁니다.

  • 질문이요 2021.04.26 10:13 댓글주소 수정/삭제 댓글쓰기

    if(confirm("정말로 삭제하겠습니까??")) 확인 버튼 클릭 시 장바구니 체크된 거 삭제하려는데요 어떻게 해야 하는지 모르겠어요ㅠㅠㅠ

    • html 파일을 보면 어떤 함수가 호출되는지 html 버튼의 onclick 이벤트 함수에 표시되어 있습니다.
      html 파일에서 basket.delAllItem 을 검색해보세요.

      자바스크립트로 confirm()을 쓰려면
      if(confirm('')){
      basket.delAllItem();
      }
      이런식으로 장바구니 처리 함수를 호출할 수 있습니다.

      html 파일의 전체 삭제 버튼의 onlclick 이벤트 처리에서 호출하는 함수는 위의 if문이 들어있는 질문자분이 새로 만든 함수명으로 변경해주어야 합니다.

  • 비밀댓글입니다

    • html 파일에서 css 파일 링크가 제대로 되어있는지 확인해보시기 바랍니다.
      ".css"로 검색하면 상단에 css파일 링크가 있습니다.

      ./*.css 로 경로가 걸려있으면 현재 경로 밑에 css 파일이라는 뜻이고...
      css 폴더를 따로 만들고 그 안에 css 파일을 넣은거면
      ./css/*.css 처럼 경로를 줘야 합니다.

  • 비밀댓글입니다

    • 장바구니 이벤트 처리 강의이기 때문에 폼 전송에 대한 처리 및 예외 처리는 제외된 것입니다.
      /order는 html의 폼이 최종 전송하는 페이지입니다.
      실제로 없으니까 최종적으로 이 페이지로 전송된다 그런 표시 정도입니다.

      수량을 수정하고 엔터를 누르면 "/order"페이지로 폼이 전송되는 액션이 발생하는 것입니다.

      장바구니에 상품이 1개만 있으면 입력 필드가 1개만 있고, 입력 필드에서 엔터를 누르면 폼이 전송됩니다.
      폼의 기본 속성이기 때문에 정상적인 현상입니다.

      이 현상을 막으려면 html 폼 태그에

      <form name="orderform" id="orderform" method="post" class="orderform" action="/Order" onsubmit="return false;">

      와 같이 끝 부분에

      onsubmit="return false;"

      이벤트 차단 처리를 해주면 됩니다.

  • 체크박스때매 머리터짐 2021.05.28 02:29 댓글주소 수정/삭제 댓글쓰기

    장바구니에서 화살표를 지우고 그냥 체크박스로만 총합과 선택된 갯수를 표현하고 싶은데 자바스크립트를 어떻게 수정해야 될지 모르겠네요 ㅜㅜ
    자바 스크립트에서 개별 수량 변경 라인을 봐도 잘 모르겠어서 그런데 혹시 알려주실 수 있으신가요? ㅜ

    • 화살표를 지우는건 그냥 html에서 다음 2개를 찾아서 지우면 해결됩니다.

      <span><i class="fas fa-arrow-alt-circle-up up"></i></span>
      <span><i class="fas fa-arrow-alt-circle-down down"></i></span>

      자바스크립트에서는

      //수량 증가 화살표 클릭
      item.children[1].addEventListener('click', function(){
      basket.changePNum(idx+1);
      });

      //수량 감소 화살표 클릭
      item.children[2].addEventListener('click', function(){
      basket.changePNum(idx+1);
      });

      이것 2개를 삭제하면 됩니다.

      그밖에는 특별히 수정할 것은 없어보입니다.
      화살표 업다운이 하는 기능은 입력필드의 값을 변경하고 재계산을 호출하는게 다입니다.

  • 체크박스때매 머리터짐 2021.05.28 19:19 댓글주소 수정/삭제 댓글쓰기

    그걸 지웠더니 아예 계산을 호출을 안하더라구요 ㅠ
    체크박스를 체크했다 안했다 할때 값이 변하지도 않고 삭제를 눌러도 합계가 안변하더라구요 ㅠ

    • 확인해서 알려드릴께요. 잠시만 기달려보세요.

    • 소스파일을 개선해서 재 업로드 했습니다.
      다시 다운로드받아 확인해보시기 바랍니다.

      체크박스 체크 여부에 따라 합계가 재계산 되게 개선했습니다.

      객체 리터럴 학습을 위한 코드라서 객체 리터럴에 필요한 기능만 구현되어 있어서 이벤트 처리에 기능적으로 다소 부족한 부분이 있습니다.

  • 체크박스때매 머리터짐 2021.05.28 21:06 댓글주소 수정/삭제 댓글쓰기

    헐 감사합니다.....

    확인해 봤는데 여전히 화살표가 없으면 체크박스로는 계산이 안되네요 ㅜㅜ

    도움주셔서 감사합니다!


닫기