전 글에선 비동기 장바구니 부분의 추가 ( insert ) 부분을 작성했다.
이제 장바구니 목록과 삭제 부분을 작성해보려고 한다.
이번글에선 장바구니 목록 ( header 부분의 비동기 목록 ) 과
장바구니 뷰 (주문으로 넘어갈 수 있는 장바구니 페이지 ) 를 같이 설명해보겠다.
장바구니 header 목록 부분은 비동기로,
장바구니 뷰 페이지는 동기 방식으로 구현되었으나, 같은 mapper를 사용한다.
(나중에 장바구니 뷰도 비동기로 할 걸 하는 후회가...)
먼저, 구현한 기능이다.
상품의 옵션별로 장바구니에 추가가 가능하고, 옵션이 중복되면 넣지 못하도록 구현했다.
장바구니에 추가 된 상품의 가격은 TOTAL 부분에 합쳐서 보이도록 했으며,
삭제하기 버튼을 누르면 해당 옵션이 삭제된다.
Mapper
<!-- 장바구니 뷰 -->
<select id="cartList" resultMap="CartVO">
select a.cart_no, a.cart_option_no, a.cart_option_content, b.item_no,
b.item_name , b.item_price ,b.item_imgmain, c.option_vol , c.option_content
from CART a left outer join ITEM b on a.cart_item_no = b.item_no
left outer join ITEMOPTION c on a.cart_option_no = c.option_no
where
<if test="cart_mem_no == 0">
cart_ckid = #{cart_ckid}
</if>
<if test="cart_mem_no != 0">
cart_mem_no = #{cart_mem_no}
</if>
</select>
<resultMap type="kr.co.vo.CartVO" id="CartVO">
<result column="CART_NO" property="cart_no" />
<result column="CART_MEM_NO" property="cart_mem_no" />
<result column="CART_ITEM_NO" property="cart_item_no" />
<result column="CART_OPTION_NO" property="cart_option_no" />
<result column="CART_OPTION_CONTENT" property="cart_option_content" />
<collection property="itemVO" resultMap="ItemVO"></collection>
<collection property="optionVO" resultMap="OptionVO"></collection>
</resultMap>
<resultMap type="kr.co.vo.ItemVO" id="ItemVO">
<result column="ITEM_NO" property="item_no" />
<result column="ITEM_NAME" property="item_name" />
<result column="ITEM_PRICE" property="item_price" />
<result column="ITEM_SIZE" property="item_size" />
<result column="ITEM_COLOR" property="item_color" />
<result column="ITEM_CONTENT" property="item_content" />
<result column="ITEM_IMGSUB" property="item_imgsub" />
<result column="ITEM_STAR" property="item_star" />
<result column="ITEM_CATEMAIN" property="item_catemain" />
<result column="ITEM_CATESUB" property="item_catesub" />
<result column="ITEM_MODEL" property="item_model" />
<result column="ITEM_IMGMAIN" property="item_imgmain" />
</resultMap>
<resultMap type="kr.co.vo.OptionVO" id="OptionVO">
<result column="OPTION_NO" property="option_no" />
<result column="OPTION_CONTENT" property="option_content" />
<result column="OPTION_ITEM_NO" property="option_item_no" />
<result column="OPTION_VOL" property="option_vol" />
</resultMap>
먼저 mapper 이다.
목록 부분이기 때문에 outer join을 이용해서 리스트를 만들었다.
resultMap의 사용의 설명은 생략하도록 하겠다.
장바구니 VO ( CartVO )
상품 VO ( ItemVO)
옵션 VO (OptionVO)
를 사용했고,
조인의 결과 값을 받기 위해
장바구니VO에 상품과 옵션VO를 import해서 getter&setter를 생성해줬다.
비회원일때는 ck_id 라는 쿠키의 value로,
회원일때는 mem_no , 회원의 PR로 조회한다.
장바구니에 담겨있는 상품의 PR과 옵션의 PR로 뷰를 구성했다.
먼저, 비동기 Header 장바구니 목록 부터 설명하겠다.
Controller
@ResponseBody
@RequestMapping(value = {"/cartHeaderView"}, method = {RequestMethod.GET})
public List<ItemVO> cartHeaderView(HttpSession session, HttpServletRequest request, HttpServletResponse response, CartVO cartVO, Model model) throws Exception {
Cookie cookie = WebUtils.getCookie(request, "cartCookie");
List<ItemVO> list = new ArrayList<>();
//비회원시 쿠키value인 ckid 사용
if (cookie != null && session.getAttribute("member") == null) {
String cartCookie = cookie.getValue();
cartVO.setCart_ckid(cartCookie);
list = mainService.cartList(cartVO);
} else if (cookie == null && session.getAttribute("member") != null) {
//회원시 mem_no이용
logger.info("카트헤더들옴??");
MemberVO memberVO = (MemberVO) session.getAttribute("member");
cartVO.setCart_mem_no(memberVO.getMEM_NO());
list = mainService.cartList(cartVO);
}
return list;
}
장바구니는 쿠키를 이용한다.
비회원일때는 cookie의 value값을 mapper에 전달해주고,
회원일때는 회원의 PR인 mem_no를 전달해준다.
그 결과값을 List에 담아서 뷰의 ajax로 리턴해준다.
쿠키의 값을 가져오는 부분이나, 세션의 값을 가져오는 부분은 전 글에 작성했으니 생략하겠다.
service나 dao도 Mapper로 파라미터를 전달해줄 뿐이기에 생략하겠다.
상단바는 모든 페이지에 include 되어있다.
상단바의 장바구니 부분 코드이다.
View
<li class="dropdown" style="font-size:15px;" id="cartLi"><a class="dropdown-toggle" href="/main/cartView" data-toggle="dropdown"><span class="icon-basket" aria-hidden="true"></span></a>
<ul class="dropdown-menu">
<!-- 장바구니 -->
<li class="dropdown" id="cartHeaderIcon">
<div style="width:550px;" class="bg-light">
<table class="table table-hover bg-light" style="">
<tbody id="cartView">
</tbody>
</table>
<hr/>
<div style="text-align:right" class=" font-serif">
<span style="font-size:22px;"> total : <span id="cartPrice"></span></span>
</div>
</div>
</li>
<li class="dropdown" style="font-size:15px;text-align:right;"><a href="/main/cartView" style="color:white">쇼핑백 보기</a></li>
</ul>
</li>
Script
$(document).ready(function(){
cartHeaderView();
})
function cartHeaderView(){
$.ajax({
url : "/main/cartHeaderView",
type : "get",
dataType : "json",
success : function(list){
var s ='';
var headersum = 0 ;
if(list<1){
s += '<div style="text-align:center"><h3>쇼핑백이 비었습니다</h3></div>' ;
}
$.each(list, function(key,value){
var item_price = parseInt(value.itemVO.item_price);
var price = new Intl.NumberFormat('ko-KR', {
style : 'currency',
currency : 'KRW'
}).format(item_price);
var cart_no = parseInt(value.cart_no);
s += '<tr> <td style="width:18%"> <a href="/main/itemContent?item_no='+value.itemVO.item_no+'"><img src="/img/'+value.itemVO.item_imgmain+'"></a></td>';
s += '<td style="vertical-align : bottom; font-size:13px;"><a href="/main/itemContent?item_no='+value.itemVO.item_no+'">'+value.itemVO.item_name+'<p>'+value.optionVO.option_content+'</p></a>';
s += '<a onclick="cartHeaderDel('+cart_no+');" style="font-size:6px" href="#"><u>삭제하기</u></a> </td>';
s += '<td style="width:20%;vertical-align : middle; font-size:13px;">'+price+'</td> </tr>';
headersum = headersum + parseInt(item_price);
});
var cartTotal = new Intl.NumberFormat('ko-KR', {
style : 'currency',
currency : 'KRW'
}).format(headersum);
$("#cartPrice").html(cartTotal);
$("#cartView").html(s);
}
})
}
일단 document.ready 이벤트를 이용해서 페이지가 로드될 때 장바구니 목록을 불러오도록 해준다.
장바구니 ajax에선 데이터를 보내주지 않는다.
컨트롤러에서 목록을 불러오는 기준인 쿠키의 value값과 세션의 회원PR을
Http 프로토콜의 Request 값에서 가져오기 때문이다.
each 함수를 이용해 리스트의 크기만큼 반복해서 목록을 #cartView에 뿌려준다.
합산가격은 each함수안에 상품의 가격이 중첩되어 합산되도록해서
#cartPrice에 보여지도록 해서 구현했다.
NumberFomat은 js에서 상품의 가격이 25000000 이라면 25,000,000 이런식으로
단위별 쉼표가 표시되게 해주는 함수이다.
그리고 목록안에 onclick 버튼을 만들었는데, onclick 함수안에 해당 장바구니의 PR을 매개변수로 담아줌으로 써,
장바구니 삭제를 할 때 필요한 장바구니PR을 보내줬다.
function cartHeaderDel(cart_no){
$.ajax({
url : "/main/cartDelete",
type : "post",
dataType : "json",
data : {"cart_no" : cart_no},
success : function(data){
cartHeaderView();
toastr.options.preventDuplicates = true;
toastr.success("삭제완료");
}
})
}
삭제 부분의 함수이다.
이제 장바구니 뷰 이다.
header의 이모티콘을 클릭하거나 장바구니 목록 가장 아래의 쇼핑백보기 를 클릭하여 들어올 수 있는 페이지이다.
이곳에서 주문페이지로 이동할 수 있다.
먼저 설명을 하자면
장바구니에 담긴 상품이 표시되도록 했으며,
상품의 수량을 선택할 수 있는데,
상품의 최대 수량이 보이도록 했으며 최대수량 이상은 선택할 수 없도록 했다.
사진에는 없지만 품절시에는 따로 표시가 되어 선택할 수 없고, 주문 할 수도 없도록 막았다.
선택한 상품의 수가 표시되고, 그 수에 맞는 상품의 가격이 표시된다.
이 부분에서는 js를 사용하여 구현했다.
Controller
@RequestMapping(value = {"/cartView"}, method = {RequestMethod.GET})
public String cartView(HttpSession session, HttpServletRequest request, HttpServletResponse response, CartVO cartVO, Model model) throws Exception {
Cookie cookie = WebUtils.getCookie(request, "cartCookie");
//비회원시 쿠키value인 ckid 사용
if (cookie != null && session.getAttribute("member") == null) {
String cartCookie = cookie.getValue();
cartVO.setCart_ckid(cartCookie);
model.addAttribute("cart", mainService.cartList(cartVO));
} else if (cookie == null && session.getAttribute("member") != null) {
//회원시 mem_no이용
MemberVO memberVO = (MemberVO) session.getAttribute("member");
cartVO.setCart_mem_no(memberVO.getMEM_NO());
model.addAttribute("cart", mainService.cartList(cartVO));
model.addAttribute("cartCount", mainService.cartMemCount(cartVO));
}
return "/main/cartView";
}
장바구니 뷰는 동기식으로 구현했으며,
마찬가지로 요청값에서 쿠키의 value값이나 세션의 회원PR로 장바구니 뷰를 구성한다.
Mapper는 비동기식과 같고 service나 dao도 매개변수를 전달할 뿐이기 때문에, 생략하도록 한다.
View
<tbody>
<tr class="bg-dark">
<th width="8%" class="hidden-xs">Item</th>
<th width="40%">Name</th>
<th width="15%">option</th>
<th class="hidden-xs" width="18%">Price</th>
<th width="10%">Quantity</th>
<th width="5%">Remove</th>
</tr>
<c:forEach items="${cart}" var="cart">
<input type="hidden" value="${cart.itemVO.item_price}" name="itemPrice" >
<input type="hidden" value="${cart.optionVO.option_no}" name="option_no" >
<input type="hidden" value="${cart.itemVO.item_no }" name="item_no" >
<input type="hidden" value="${cart.itemVO.item_imgmain}" name="item_img" >
<input type="hidden" value="${cart.itemVO.item_name}" name="item_name" >
<input type="hidden" value="${cart.optionVO.option_content}" name="item_option" >
<input type="hidden" value="${cart.itemVO.item_price}" name="item_price" >
<input type="hidden" value="${cart.optionVO.option_vol}" name="optionVol">
<tr
<c:if test="${cart.optionVO.option_vol == 0}">
class="active"
</c:if>
>
<td class="hidden-xs" height="5px"><a href="/main/itemContent?item_no=${cart.itemVO.item_no}"><img src="/img/${cart.itemVO.item_imgmain}" alt="Accessories Pack"/></a></td>
<td style="vertical-align : middle;">
<h5 class="product-title font-alt"><a href="/main/itemContent?item_no=${cart.itemVO.item_no}"> ${cart.itemVO.item_name} </a></h5>
</td>
<td style="vertical-align : middle">
<h5 class="product-title font-alt">${cart.optionVO.option_content}</h5>
</td>
<td class="hidden-xs" style="vertical-align : middle">
<h5 class="product-title font-alt">₩ <fmt:formatNumber pattern="###,###,###" value="${cart.itemVO.item_price}"/> </h5>
</td>
<td style="vertical-align : middle">
<c:if test="${cart.optionVO.option_vol == 0}">
<h4>품절</h4>
</c:if>
<c:if test="${cart.optionVO.option_vol != 0}">
<input class="form-control" type="number" name="select_vol" id="select_vol" value="1" max="${cart.optionVO.option_vol}"
min="1" onchange="itemTotal();" title="남은 수량 : ${cart.optionVO.option_vol}" style="width: 70%;"/>
</c:if>
</td>
<td style="vertical-align : middle" class="pr-remove"><a onclick="cartDelete(${cart.cart_no}); return flase;" title="Remove"><i class="fa fa-times" style="font-size:30px;"></i></a></td>
</tr>
</c:forEach>
</tbody>
<!-- 나머지 부분 생략 -->
<h4 class="font-alt">Cart Totals</h4>
<table class="table table-border checkout-table">
<tbody>
<tr >
<th width="25%" style="vertical-align : middle;">Items : </th>
<td id="itemTotalVol" style="font-size : 20pt" ></td>
</tr>
<tr class="shop-Cart-totalprice ">
<th style="vertical-align : middle;">Total :</th>
<td id="itemTotalPrice" style="font-size : 20pt"></td>
</tr>
</tbody>
</table>
<input id="totalPrice" type="hidden" name="totalPrice" >
<input id="totalVol" type="hidden" name="totalVol">
<button class="btn btn-lg btn-block btn-round btn-d" type="button" onclick="orderBtn(); return false;">Proceed to Checkout</button>
</div>
script 부분
<script>
$(document).ready(function(){
itemTotal();
});
function cartDelete(cart_no){
$.ajax({
type : "POST",
url : "/main/cartDelete",
dataType : "json",
data : {"cart_no" : cart_no},
error : function(request, status, error){
alert("code:"+request.status+"\n"+"message:"+request.responseText+"\n"+"error:"+error);
},
success : function(data){
if(data == 1){
alert("삭제완료");
location.reload();
}
}
});
}
function cartDeleteAll(){
$.ajax({
type : "POST",
url : "/main/cartDeleteAll",
dataType : "json",
error : function(request, status, error){
alert("code:"+request.status+"\n"+"message:"+request.responseText+"\n"+"error:"+error);
},
success : function(data){
if(data == 1){
alert("삭제완료");
location.reload();
}
}
});
}
function orderBtn(){
console.log("들어옴?");
if(${member == null }){
toastr.options.preventDuplicates = true;
toastr.warning("주문하시려면 로그인을 해주세요");
return false;
}
var optionVol = $('input[name="optionVol"]');
var sum = 0;
var count = optionVol.length;
console.log(count);
var optionMax= $('input[name="select_vol"]');
for(var i = 0; i < count; i++){
sum = parseInt(optionVol[i].value);
summ = parseInt(optionMax[i].value);
console.log(summ);
if(sum == 0){
toastr.options.preventDuplicates = true;
toastr.warning("품절상품은 주문하실 수 없습니다");
return false;
}else if(sum < summ){
toastr.options.preventDuplicates = true;
toastr.warning("최대수량을 넘겨서 주문하실 수 없습니다");
return false;
}
}
var formObj = $("form[name='orderForm']");
formObj.attr("action", "/main/orderView");
formObj.attr("method", "post");
formObj.submit();
}
function itemTotal() {
console.log("토탈들어옴?");
var itemPrice = $('input[name="itemPrice"]');
var selectvol = $('input[name="select_vol"]');
var sum = 0;
var count = itemPrice.length;
var itemvol = 0;
for (var i = 0; i < count; i++){
sum += parseInt(itemPrice[i].value) * parseInt(selectvol[i].value);
itemvol += parseInt(selectvol[i].value);
}
var summ = new Intl.NumberFormat('ko-KR', { style: 'currency', currency: 'KRW' }).format(sum);
$("#itemTotalPrice").html(summ);
$("#itemTotalVol").html(itemvol);
$("#totalPrice").val(sum);
$("#totalVol").val(itemvol);
}
</script>
script는 4가지 function이 있다.
장바구니 삭제, 전체삭제, 주문 , 그리고 가격을 합산해주는 부분이다.
장바구니 삭제에 대한 코드는 사실 너무 간단하기 떄문에 나중에 몰아서 코드만 올려놓으려고 한다..
먼저 총액을 만들어주는
itemTotal() 이다.
먼저, cartView의 장바구니에 담긴 상품은
cart테이블을 select해서 나오는 List이다.
그 List를 forEach로 뷰에 출력할 때, input hidden으로 itemPrice와 select_vol이라는 두개의 input태그를 만들었다.
여러개의 List가 출력되는데, 하나의 input태그 name값을 가지고 있다.
때문에 해당 input태그는 배열로 출력되는데, var count에 itemPrice의 length를 담는다.
배열의 길이만큼 for문을 돌려서
가격 (itemPrice) * 수량(select_vol) 을 sum += 해준다
그 값을 div의 id값에 맞춰서 보내주면 된다.
그 다음은 orderBtn() 이다.
주문버튼을 누르면 주문페이지로 넘어가는 함수이다.
마찬가지로 input태그의 값을 가져와서 for문을 이용해 [i]의 값을 주고,
품절수량 및 최대 수량을 검사한 후, 문제가 없다면 주문페이지로 이동시켜주는 함수이다.
form 태그를 submit하여, 주문페이지로 넘긴다.
form 태그안에는 장바구니에 속해있는 item과 option들의 값이 담겨있다.
장바구니 조회 부분을 완료했다.
다음 글엔 간단하게 장바구니 삭제를 설명하고, 주문으로 넘어가보려고 한다.
'SPRING > Homme Shop' 카테고리의 다른 글
[스프링] 쇼핑몰 - 상품 주문 [ 2 ] (0) | 2021.12.09 |
---|---|
[스프링] 쇼핑몰 - 상품 주문 [ 1 ] (0) | 2021.12.08 |
[스프링] 쇼핑몰 - 비동기 장바구니 [3] (0) | 2021.12.08 |
[스프링] 쇼핑몰 - 비동기 장바구니 [1] (6) | 2021.12.08 |
[스프링]쇼핑몰 - 상품 등록 (0) | 2021.12.07 |