스프링 쇼핑몰 만들기 #22. 카트 삭제 기능 구현
원하는 상품을 카트에 담아두는 기능을 구현했으니, 담아둔 상품을 삭제하는 기능을 구현하겠습니다.
상품 삭제나 소감(댓글) 삭제처럼 하나하나 지우는 기능 말고도 여러개의 목록을 한번에 삭제해야하는 기능이 필요로 합니다. 여기서는 체크박스를 이용해 지우고자 하는 목록을 체크하고 그 체크된 목록을 지우는 기능과, 개별적으로 지우는 기능 두가지를 구현하겠습니다.
cartList.jsp의 목록 반복문에 코드를 추가하고, 삭제 버튼에 data-cartNum 속성을 추가합니다.
<ul>
<li>
<div class="allCheck">
<input type="checkbox" name="allCheck" id="allCheck" /><label for="allCheck">모두 선택</label>
</div>
<div class="delBtn">
<button type="button" class="selectDelete_btn">선택 삭제</button>
</div>
</li>
<c:forEach items="${cartList}" var="cartList">
<li>
<div class="checkBox">
<input type="checkbox" name="chBox" class="chBox" data-cartNum="${cartList.cartNum}" />
</div>
<div class="thumb">
<img src="${cartList.gdsThumbImg}" />
</div>
<div class="gdsInfo">
<p>
<span>상품명</span>${cartList.gdsName}<br />
<span>개당 가격</span><fmt:formatNumber pattern="###,###,###" value="${cartList.gdsPrice}" /> 원<br />
<span>구입 수량</span>${cartList.cartStock} 개<br />
<span>최종 가격</span><fmt:formatNumber pattern="###,###,###" value="${cartList.gdsPrice * cartList.cartStock}" /> 원
</p>
<div class="delete">
<button type="button" class="delete_btn" data-cartNum="${cartList.cartNum}">삭제</button>
</div>
</div>
</li>
</c:forEach>
</ul>
스타일도 추가합니다.
.allCheck { float:left; width:200px; }
.allCheck input { width:16px; height:16px; }
.allCheck label { margin-left:10px; }
.delBtn { float:right; width:300px; text-align:right; }
.delBtn button { font-size:18px; padding:5px 10px; border:1px solid #eee; background:#eee;}
.checkBox { float:left; width:30px; }
.checkBox input { width:16px; height:16px; }
목록의 가장 위에 모두 선택할 수 있는 체크박스와 선택 삭제 버튼이 생겼고, 각 목록의 가장 앞(왼쪽)에 체크박스가 생겼습니다.
체크박스 밑에 각각 코드를 추가합니다.
<script>
$("#allCheck").click(function(){
var chk = $("#allCheck").prop("checked");
if(chk) {
$(".chBox").prop("checked", true);
} else {
$(".chBox").prop("checked", false);
}
});
</script>
모두 선택 체크박스에 체크를 하게되면, 개별 체크박스(.chBox)들도 모두 체크가 되는 스크립트입니다.
<script>
$(".chBox").click(function(){
$("#allCheck").prop("checked", false);
});
</script>
개별 체크박스가 선택되거나 선택해제되면 모두 선택 체크박스가 해체되는 스크립트입니다.
제이쿼리의 선택자를 사용하고 있으므로, <head> ~ </head> 내부에 제이쿼리 CDN을 추가합니다.
<script src="/resources/jquery/jquery-3.3.1.min.js"></script>
모두 선택 체크박스를 체크하거나 체크해제하게되면 개별 체크 박스들도 따라서 바뀝니다.
개별 체크 박스 하나를 체크해제하면, '모두 선택'된게 아니기 때문에 모두 선택 체크박스가 해제됩니다.
선택 삭제 버튼 밑에 스크립트를 추가합니다.
<script>
$(".selectDelete_btn").click(function(){
var confirm_val = confirm("정말 삭제하시겠습니까?");
if(confirm_val) {
var checkArr = new Array();
$("input[class='chBox']:checked").each(function(){
checkArr.push($(this).attr("data-cartNum"));
});
$.ajax({
url : "/shop/deleteCart",
type : "post",
data : { chbox : checkArr },
success : function(){
location.href = "/shop/cartList";
}
});
}
});
</script>
confirm을 이용해 삭제 여부를 확인하고, 개별 선택된 체크박스들을 배열 변수 checkArr에 저장한 뒤 컨트롤러로 전송합니다. 전송이 이상없이 되었다면 현재 페이지(/shop/cartList)를 새로고침합니다.
카트 목록을 삭제하는 쿼리를 테스트해봅니다. 삭제 조건에는 카트 번호(cartNum)와 아이디(userId) 두가지입니다.
매퍼에 쿼리를 추가합니다.
<!-- 카트 삭제 -->
<delete id="deleteCart">
delete tbl_cart
where cartNum = #{cartNum}
and userId = #{userId}
</delete>
DAO와 Service를 작성합니다.
컨트롤러에 카트 삭제용 메서드를 추가합니다.
// 카트 삭제
@ResponseBody
@RequestMapping(value = "/deleteCart", method = RequestMethod.POST)
public int deleteCart(HttpSession session,
@RequestParam(value = "chbox[]") List<String> chArr, CartVO cart) throws Exception {
logger.info("delete cart");
MemberVO member = (MemberVO)session.getAttribute("member");
String userId = member.getUserId();
int result = 0;
int cartNum = 0;
if(member != null) {
cart.setUserId(userId);
for(String i : chArr) {
cartNum = Integer.parseInt(i);
cart.setCartNum(cartNum);
service.deleteCart(cart);
}
result = 1;
}
return result;
}
에이젝스에서 전송받는 배열 chbox를 리스트형 변수 chArr로 받은 뒤, for문을 이용해 chArr이 가지고 있는 값의 갯수만큼 반복합니다.
이 컨트롤러에서도 변수 result를 이용해 0 또는 1의 결과를 반납하게됩니다. 로그인이 안되었거나 세션이 만료되어 자동 로그아웃된 경우 작동하기 않게 하기 위함입니다.
물론 이 코드가 없더라도 카트가 삭제되지 않고 에이젝스의 error를 이용해 구분할 수 있지만, 컨트롤러보다 더 깊은 Service와 DAO를 거쳐 쿼리문이 실행되는걸 막을 수 있습니다.
지금은 Service나 DAO에 별다른 코드가 없고 쿼리도 단순하기 때문에 속도나 데이터적으로 큰 차이는 없으나, 나중에 규모가 커진다면 다를것이며, 무엇보다 불필요한 소모는 줄이는게 맞습니다.
컨트롤러에서 돌려받는 result를 이용해 결과를 구분합니다.
$.ajax({
url : "/shop/deleteCart",
type : "post",
data : { chbox : checkArr },
success : function(result){
if(result == 1) {
location.href = "/shop/cartList";
} else {
alert("삭제 실패");
}
}
});
모두 선택 체크박스에 체크를 하거나, 원하는 목록에 체크를 한 뒤 선택 삭제 버튼을 클릭하면 삭제 여부를 묻는 창이 뜨고, 여기서 확인을 누르게되면
삭제됩니다.
이번엔 목록에 있는 삭제 버튼 아래에 스크립트를 추가합니다.
<script>
$(".delete_${cartList.cartNum}_btn").click(function(){
var confirm_val = confirm("정말 삭제하시겠습니까?");
if(confirm_val) {
var checkArr = new Array();
checkArr.push($(this).attr("data-cartNum"));
$.ajax({
url : "/shop/deleteCart",
type : "post",
data : { chbox : checkArr },
success : function(result){
if(result == 1) {
location.href = "/shop/cartList";
} else {
alert("삭제 실패");
}
}
});
}
});
</script>
기본적인 구조는 위에서 했던 선택 삭제의 스크립트와 거의 일치합니다. 차이점은 checkArr에 값을 넣어주는 checkArr.push($(this).attr("data-cartNum"));
부분뿐입니다.
이렇게하면 작동이 이상하게 되는데, 그 이유는 삭제 버튼의 클래스명은 2개 이상이 존재하기 때문입니다. 그러므로 각 버튼을 구분할 수 있는 고유한 클래스명이 필요로 합니다.
이미 목록들이 카트 번호(cartNum)로 구분되기 때문에, 클래스와 스크립트에도 카트 번호를 포함시켜줍니다.
<div class="delete">
<button type="button" class="delete_${cartList.cartNum}_btn" data-cartNum="${cartList.cartNum}">삭제</button>
<script>
$(".delete_${cartList.cartNum}_btn").click(function(){
var confirm_val = confirm("정말 삭제하시겠습니까?");
if(confirm_val) {
var checkArr = new Array();
checkArr.push($(this).attr("data-cartNum"));
$.ajax({
url : "/shop/deleteCart",
type : "post",
data : { chbox : checkArr },
success : function(result){
if(result == 1) {
location.href = "/shop/cartList";
} else {
alert("삭제 실패");
}
}
});
}
});
</script>
</div>
목록 하나만 지우는데 배열(checkArr)을 사용하는 이유는, 컨트롤러에 있는 메서드를 그대로 사용하기 위함입니다.
컨트롤러를 선택형과 개별형으로 구분시켜놓더라도, 같은 Service와 DAO, 매퍼를 사용하기 때문에 굳이 구분할 필요가 없습니다.
실행해서 버튼의 클래스와 스크립트를 보면 카트 번호가 부여된걸 확인할 수 있습니다.
삭제 버튼을 클릭하면
목록 하나만 삭제된걸 확인할 수 있습니다.
안녕하세요. 정말 감사히 잘 보면서 공부하고 있습니다. 한가지 질문이 있습니다. 버튼에 data-cartNum 속성을 추가해 주셨는데 이게 무얼의미하는지 알 수 있을까요?
답글삭제안녕하세요?
삭제먼저 data-cartNum는 커스텀 속성입니다. 커스텀 속성은 data-[사용자 문자]로 구성되어있으며, [사용자 문자]는 아무 문자나 사용할 수 있습니다.
버튼에 data-cartNum를 추가한 이유는, 버튼을 클릭했을 때 해당 카트번호(cartNum)를 가져오기 쉽도록 하기 위함입니다.
카트번호를 이용하여 카트에 담긴 상품을 삭제하거나, 주문하는 용도에 쓰이는데
이처럼 버튼에 커스텀 속성으로 값을 넣게되면, $(this).attr("data-cartNum")로 쉽게 가져올 수 있습니다.
안녕하세요 ! 열심히 참고해서 보고는 있습니다만
답글삭제다름이아니라 컨트롤러로 데이터 전달은 잘되지만
cartNum = Integer.parseInt(a); 이부분에서 캐스팅 에러가 나옵니다.
방법이 있을까요?
질문 있습니다.
답글삭제data : {chbox : checkArr}부분에서 'chbox'부분은 class명 'chBox'와 동일한 것인가요?
컨트롤러에서는 chArr가 잘찍히는데요, 음 Ajax처리에서 data 값이 자꾸 undefined로 뜨네요,, ㅠㅠ
ajax 처리하기전에 콘솔로그 찍으셔서 확인하셔야할듯.
삭제쿠즈로 선생님 정말 감사합니다.
답글삭제선생님의 유익한 강의로 진행중인 프로젝트 학습에 매우 큰 도움을 받고있습니다.
저와 가까운 곳에 계신 분이라면 정말 어떻게든 답례를 해드리고싶을정도입니다.
쿠즈로 선생님을 통해 코딩에 재미가 붙기 시작했습니다. 너무너무 감사하고 존경스럽니다 쿠즈로 선생님 최고!
안녕하세요? 방문해주셔서 감사합니다.
삭제기는듯 구르는듯 운영하고있는데 도움이 되셨으니 다행입니다.
그냥 취미로 하고있는거라.. 업로드가 나날이 밀려나고 있어서 드릴말씀이 없네요ㅠㅠ..
Required List parameter 'chbox[]' is not present]
답글삭제이게 계속뜨네요.. ajax에서 걸리는데 컨트롤 문제인거같아요..
안녕하세요? 방문해주셔서 감사합니다.
답글삭제컨트롤러에서 @RequestParam(value = "chbox[]") List<String> chArr를 못받는다면.. 에이젝스로 건내주는 값의 이름이 chbox가 아니거나(대소문자 구분) 동일한 데이터형인지 확인해야할 것 같습니다.
각 제품을 삭제할 때, 클래스 명을 구분하는 이유가 뭔가요?
답글삭제이상하게 동작된다고 하셨는데, 저는 문제없이 잘 작동돼서 질문드려요.
checkArr.push($(this).attr("data-cartNum"));
여기 보면, 클릭한 버튼의 data-cartNum를 배열에 넣는 거라 잘 작동되는 게 맞지 않나요?
안녕하세요? 방문해주셔서 감사합니다.
삭제블로그의 본문대로 진행하게되면...
각 리스트는 모두 동일한 클래스를 가지고 있습니다.
그 상태에서 클릭했을때, 무엇을 클릭했는지 알 수 없기 때문에
1. this를 사용
2. 리스트를 만드는 테이블의 고유값 사용
3. 본문처럼 별도로 번호를 부여한 값(data-cartNum)을 사용
등등의 방식을 사용하게 됩니다.
요점은 해당 항목의 고유값을 구분하기 위함입니다.