20221014_fri
실습내용
- 장바구니 등록 쿼리 수정(merge into 사용)
- 변경버튼 클릭시 상품수량 변경 ->ajax 사용 (java 상관없음)
: 정말 변경하시겠습니까?(modal창띄우기) -> 확인 클릭시, 변경했습니다.(modal)
- '선택삭제'
: 체크된 상품이 있는지 검사 -> 삭제(modal)
: 정말 삭제하시겠습니까?(modal창띄우기) -> 확인 클릭시, 삭제했습니다.(modal)
- 상품 상세페이지에서 수량변경시 총 가격도 자동계산 만들기 (자바스크립트 이용)
package Kh.study.shop.cart.controller;
@Controller
@RequestMapping("/cart")
public class CartController {
@Resource(name = "cartService")
private CartService cartService;
@ResponseBody //ajax사용 하니까
@PostMapping("/regCart")
public void regCart(CartVO cartVO ,Authentication authentication) {
User user = (User)authentication.getPrincipal();
cartVO.setMemberId(user.getUsername());
cartService.regCart(cartVO);
}
// ajax에서 if문으로 result값이 true일때 이동하는 페이지 경로
@GetMapping("/cartList")
public String cartList(Model model,Authentication authentication) {
// memberId 값 가져와서 cartList에 같이 던져주기
User user = (User)authentication.getPrincipal();
model.addAttribute("cartList",cartService.selectCartList(user.getUsername())) ;
// 전체 총 가격 데이터 (리스트에서 뽑아서 누적합으로 데이터 던져주기)
int finalPrice = 0;
for(CartVO e : cartService.selectCartList(user.getUsername())) {
finalPrice = finalPrice + e.getTotalPrice();
}
model.addAttribute("finalPrice",finalPrice);
return "content/cart/cart_list";
}
//장바구니 상품 수량변경
@ResponseBody
@PostMapping("/updateAmount")
public void updateAmount(CartVO cartVO) {
cartService.updateAmount(cartVO);
}
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--해당 파일에 모든 쿼리문을 작성 -->
<mapper namespace="cartMapper">
<resultMap type="Kh.study.shop.cart.vo.CartVO" id="cart">
<id column="CART_CODE" property="cartCode"/>
<result column="ITEM_CODE" property="itemCode"/>
<result column="MEMBER_ID" property="memberId"/>
<result column="CART_AMOUNT" property="cartAmount"/>
<result column="REG_DATE" property="regDate"/>
<result column="TOTAL_PRICE" property="totalPrice"/>
<!-- 내가 만든 ver. 그냥 변수추가하기 -->
<result column="CATE_NAME" property="cateName"/>
<result column="ITEM_NAME" property="itemName"/>
<result column="ITEM_PRICE" property="itemPrice"/>
<result column="ATTACHED_NAME" property="attachedName"/>
<!-- 선생님 ver. 단, cartVO에 itemVO 추가하기 -->
<association property="itemVO" resultMap="itemMapper.item"></association>
</resultMap>
<resultMap type="Kh.study.shop.item.vo.CategoryVO" id="category">
<id column="CATE_CODE" property="cateCode"/>
<result column="CATE_NAME" property="cateName"/>
<result column="CATE_STATUS" property="cateStatus"/>
</resultMap>
<resultMap type="Kh.study.shop.item.vo.ItemVO" id="item">
<id column="ITEM_CODE" property="itemCode"/>
<result column="ITEM_NAME" property="itemName"/>
<result column="ITEM_PRICE" property="itemPrice"/>
<result column="ITEM_COMMENT" property="itemComment"/>
<result column="REG_DATE" property="regDate"/>
<result column="ITEM_STOCK" property="itemStock"/>
<result column="ITEM_STATUS" property="itemStatus"/>
<result column="CATE_CODE" property="cateCode"/>
<association property="categoryVO" resultMap="adminMapper.category" />
<collection property="imgList" resultMap="img"/>
</resultMap>
<resultMap type="Kh.study.shop.item.vo.ImgVO" id="img">
<id column="IMG_CODE" property="imgCode"/>
<result column="ORIGIN_NAME" property="originName"/>
<result column="ATTACHED_NAME" property="attachedName"/>
<result column="IS_MAIN" property="isMain"/>
<result column="ITEM_CODE" property="itemCode"/>
</resultMap>
<!-- 장바구니 목록조회 -->
<!-- 장바구니를 reg_date를 조회할 때 주의할 점: 반드시 장바구니에 등록한 날짜를 기준으로 해야한다. -->
<!-- 장바구니등록된 날짜 : C.REG_DATE -->
<select id="selectCartList" resultMap="cart">
SELECT CART_CODE
,IT.CATE_CODE
,CATE_NAME
,C.ITEM_CODE
,ITEM_NAME
,MEMBER_ID
,CART_AMOUNT
,TO_CHAR(C.REG_DATE,'YYYY-MM-DD') REG_DATE
,ITEM_PRICE
,TOTAL_PRICE
,ATTACHED_NAME
FROM SHOP_CART C,SHOP_ITEM IT,ITEM_IMG IM,ITEM_CATECGORY CT
WHERE C.ITEM_CODE = IT.ITEM_CODE
AND C.ITEM_CODE = IM.ITEM_CODE
and IT.CATE_CODE = CT.CATE_CODE
AND IS_MAIN = 'Y'
AND MEMBER_ID = #{memberId}
ORDER BY CART_CODE DESC
</select>
<!-- 장바구니 등록 (같은 상품이어도 별도로 등록됨)-->
<!-- totalPrice : 단가 * 수량 = 단가 * #{cartAmount} -->
<!-- 단가 : 서브쿼리로 구한다. -->
<!-- <insert id="regCart">
<selectKey resultType="String" keyProperty="cartCode" order="BEFORE">
SELECT 'CART_' ||LPAD(NVL(MAX(TO_NUMBER(SUBSTR(CART_CODE,6))),0)+1,3,0)
FROM SHOP_CART
</selectKey>
INSERT INTO SHOP_CART
( CART_CODE
,ITEM_CODE
,MEMBER_ID
,CART_AMOUNT
,TOTAL_PRICE
) VALUES (
#{cartCode},#{itemCode},#{memberId},#{cartAmount}
, (SELECT ITEM_PRICE FROM SHOP_ITEM WHERE ITEM_CODE = #{itemCode}) * #{cartAmount}
)
</insert> -->
<!-- 장바구니 등록쿼리 Merge 사용(같은상품끼리 자동 업데이트 묶어서 등록) -->
<insert id="regCart">
<selectKey resultType="String" keyProperty="cartCode" order="BEFORE">
SELECT 'CART_'|| LPAD(NVL(MAX(TO_NUMBER(SUBSTR(CART_CODE, 6))), 0) +1, 3, 0) FROM SHOP_CART
</selectKey>
MERGE INTO SHOP_CART
USING DUAL
ON(ITEM_CODE = #{itemCode} AND MEMBER_ID = #{memberId})
WHEN MATCHED THEN
UPDATE
SET
CART_AMOUNT = CART_AMOUNT + #{cartAmount}
, TOTAL_PRICE = (CART_AMOUNT + #{cartAmount}) *
(SELECT ITEM_PRICE FROM SHOP_ITEM WHERE ITEM_CODE = #{itemCode})
WHERE MEMBER_ID = #{memberId}
AND ITEM_CODE = #{itemCode}
WHEN NOT MATCHED THEN
INSERT( CART_CODE
, ITEM_CODE
, MEMBER_ID
, CART_AMOUNT
, TOTAL_PRICE
)VALUES(
#{cartCode}
, #{itemCode}
, #{memberId}
, #{cartAmount}
, (SELECT ITEM_PRICE FROM SHOP_ITEM
WHERE ITEM_CODE = #{itemCode}) * #{cartAmount}
)
</insert>
<!-- 재고 상품 수량 변경 -->
<!-- 수량이 바뀌면 총가격도 바뀌어야한다! 주의하기 -->
<!-- cat_code 값을 알면 item_Cdoe도 알수 있다! -> 이 점을 이용해서 이중서브쿼리문! -->
<update id="updateAmount">
UPDATE SHOP_CART
SET
CART_AMOUNT = #{cartAmount}
, TOTAL_PRICE = ( SELECT ITEM_PRICE
FROM SHOP_ITEM
WHERE ITEM_CODE =
(
SELECT ITEM_CODE
FROM SHOP_CART
WHERE CART_CODE = #{cartCode}
)
)
* #{cartAmount}
WHERE CART_CODE = #{cartCode}
</update>
</mapper>
package Kh.study.shop.cart.service;
public interface CartService {
//장바구니 조회
List<CartVO> selectCartList(String memberId);
//장바구니 등록
void regCart(CartVO cartVO);
//상품수량 변경
void updateAmount(CartVO cartVO);
}
package Kh.study.shop.cart.service;
@Service("cartService")
public class CartServiceImpl implements CartService {
@Autowired
private SqlSessionTemplate sqlSession;
@Override
public List<CartVO> selectCartList(String memberId) {
return sqlSession.selectList("cartMapper.selectCartList",memberId);
}
@Override
public void regCart(CartVO cartVO) {
sqlSession.insert("cartMapper.regCart",cartVO);
}
// 장바구니 상품 수량 변경
@Override
public void updateAmount(CartVO cartVO) {
sqlSession.insert("cartMapper.updateAmount",cartVO);
}
}
// [변수 선언] ------------------------------------------------------------------------//
//제목 행의 체크박스의 체크여부를 확인
const allChk = document.querySelector('#allChk');
//제목줄 제외 모든 체크박스
const chks = document.querySelectorAll('.chk');
//모달창(수량변경)
const updateAmountModal = new bootstrap.Modal('#updateAmountModal');
////////////////////////////// 이벤트 정의 영역 ///////////////////////////////////////////
//[이벤트 1] 체크박스 전체 선택
allChk.addEventListener('click',function(){// allchk 값은 하나만 있기 때문 이벤트 가능!
const isChecked = allChk.checked;//체크된 제목줄 체크박스를 isChecked라고 한다.
const chk = document.querySelectorAll('.chk');// 테이블의 체크된 체크박스
//제목 줄의 체크박스가 체크 됐다면..
if(isChecked){//체크해ㅏㄹ
for(i = 0; i<chk.length; i++){
chk[i].checked = true;//체크된 클래스가 chk의 i번째 값은 true로 준다.
}
}
else{//체크해제해라
for(i = 0; i<chk.length; i++){
chk[i].checked = false;//체크된 클래스가 chk의 값은 true로 준다.
}
}
//전체 체크된 가격 총가격 나타내기
setFinalPrice();
});
// [이벤트 2] 체크박스 전체해제
// event for문 밖 변수선언
const chk = document.querySelectorAll('.chk');
for(const e of chk){
e.addEventListener('click',e=>{
const cnt = chk.length; //제목줄 밑의 전체 체크박스의 갯수는 cnt
const checkedCnt = document.querySelectorAll('.chk:checked').length; //클래스가 chk 인 것들 중에서 체크된것만 들고오겠다.그것의 갯수.
if(cnt == checkedCnt){// 체크해라
document.querySelector('#allChk').checked = true;
}
else{//체크하지마라
document.querySelector('#allChk').checked = false;
}
//전체 체크된 가격 총가격 나타내기
//setFinalPrice();
});
}
// [이벤트 3 ]장바구니 목록에 있는 체크박스 클릭 시 진행
// !!! 값이 여러개에 이벤트 불가능! -> for문돌려서 하나씩 빼서 여러번 실시한다.
for(const chk of chks){
chk.addEventListener('click',function(){//체크박스 클릭시 실행
setFinalPrice();//체크된 가격 하나씩 빼서 총가격 누적합계산하겠다.
});
}
///////////////////////////////////// [함수 정의] ///////////////////////////////////////////////////////////////////////////
//[함수 3] 총가격을 세팅하는 함수
function setFinalPrice(){
//체크된 박스 태그 선택
const checkedBoxes = document.querySelectorAll('.chk:checked');
//체크된 체크박스의 totalPrice 값 선택
let finalPrice = 0;
for(const checkBox of checkedBoxes){ // parseInt : 자바스크립트_숫자를 문자로 변환
const totalPrice = parseInt(checkBox.closest('tr').querySelector('.totalPriceDiv').dataset.totalPrice);
// 만약 data-totalprice로 데이터를 가져온다면?(th: 타임리프 사용해야 변수값 들고온다)
finalPrice = finalPrice + totalPrice;
}
//선택된 전체 값 더하기
// 천단위 구분 기호 : .toLocaleString()
document.querySelector('#finalPriceTag').innerText = '₩'+ finalPrice.toLocaleString() ;
}
// [함수 4 ] 수량변경 버튼 클릭시 진행되는 함수
function goUpdateAmount(selectedTag){// selectedTag :this로 넘어오는 태그(수량값의 변경버튼 클릭한 태그 의미)
updateAmountModal.show();//맨위에 선언한 변수 모달을 가져와 열어주도록 한다.
// 수량값과 카트코드값을 들고와야하기때문에 태그 선택해서 가져오기! 어떻게?
// 클릭한 버튼의 data-cart-code, data-cart-amount 속성의 값을 수량을 변경하고자하는 데이터로 세팅
// 내가 지금 클릭한 태그의 바로 옆에있는 input태그의 수량값 들고오자. -> this 사용하기
// - 수량 (cartAmount)
// --1) input태그 number값으로 수량 태그 선택시 :
const cartAmount = selectedTag.closest('tr').querySelector('input[type="number"]').value;
// --2) id값으로 수량 태그 선택시 : const cartAmountB = selectedTag.querySelector('#cartAmounts').value;
// - 카트코드 (cartCode): 체크박스에다가 카트코드 부여하기 value값으로
const cartCode = selectedTag.closest('tr').querySelector('input[type="checkbox"]').value;
// - 변경된 수량 태그선택
//const itemPrice = document.querySelector('#itemPriceTag').value;
//alert(itemPrice);
// data- 속성값에 위에서 선택한 태그값들을 바로 넣어준다.
document.querySelector('#updateAmountBtn').dataset.cartCode = cartCode;
document.querySelector('#updateAmountBtn').dataset.cartAmount = cartAmount;
//document.querySelector('#updateAmountBtn').dataset.itemPrice = cartAmount;
}
// [함수 5] 수량버튼 클릭 후, 나타나는 모달창의 확인버튼 클릭시, 진행되는 함수
function updateAmount(){// 여기 괄호안 매개변수는 html연결된 데이터!
const cartCode = document.querySelector('#updateAmountBtn').dataset.cartCode;
const cartAmount = document.querySelector('#updateAmountBtn').dataset.cartAmount;
//const itemPrice = document.querySelector('#updateAmountBtn').dataset.itemPrice;
$.ajax({
url: '/cart/updateAmount', //요청경로
type: 'post',
// 여기 데이터는 controller에서 던져받는 데이터!
data: {'cartCode':cartCode
,'cartAmount':cartAmount}, //필요한 데이터
success: function(result) {
alert('성공');
//주의!! 모달창은 알아서 닫히지않는다. 페이지이동을 하지않은 이상! 때문에 코드를 직접 써서 닫아줘야한다.
updateAmountModal.hide();
//모달창이 닫히자마자 input태그 입력한 상품수량값으로 옆에 수량도 바뀌어야 한다.
},
error: function() {
alert('실패');
}
});
}
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultra.net.nz/thymeleaf/layout"
layout:decorate="~{layout/base_layout}"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
<div layout:fragment="content">
<!-- 제목줄 -->
<div class="row mb-5">
<div class="col">
<h1>CART LIST</h1>
</div>
</div>
<!-- 장바구니 목록 테이블 -->
<div class="row ">
<div class="col ">
<table style="width: 100%;" class="table table-striped table-hover">
<colgroup>
<col width="*%" >
<col width="5%" >
<col width="15%" >
<col width="20%" >
<col width="25%" >
<col width="20%" >
<col width="20%" >
</colgroup>
<thead style="font-size: 20px; font-weight: bold; color: #425F57;">
<tr >
<td><input type="checkbox" id="allChk" checked="checked"></td>
<td>No.</td>
<td colspan="2" style="text-align: center;">상품 정보</td>
<td>수량</td>
<td>가격</td>
<td>등록 일자</td>
</tr>
</thead>
<tbody align="center" style="text-align: justify;">
<th:block th:if="${#lists.size(cartList) == 0}">
<div> 장바구니에 등록된 상품이 없습니다.</div>
</th:block>
<th:block th:unless="${#lists.size(cartList) == 0}">
<th:block th:each="cart : ${cartList}" >
<tr> <!-- 만약, td태그 밖에서 데이터 화면에 나타나게한다면? -> [[${cart.cateName}]] -->
<td><input th:value="${cart.cartCode}" type="checkbox" class="chk" checked="checked"></td>
<!-- 역순으로 순서 뽑는 방법 : 전체갯수 - index -->
<!-- 순서(No) 뽑는방법 : [[${cartStat.index}]] 이용 -->
<!-- 차례대로 순서 뽑는방법 : <th:block th:each="cart , status : ${cartList}" > <td th:text="${status.count}"> -->
<td>
<span th:text="${#lists.size(cartList) - cartStat.index}"></span>
</td>
<td ><img width="100px;" height="140px;" th:src="|@{/images/}${cart.attachedName}|"> </td>
<td >
[<span style="color: #749F82;" th:text=" ${cart.cateName}"></span>]
<span th:text=" ${cart.itemName}" ></span>
</td>
<td >
<input id="cartAmounts" type="number" min="0" th:value="${cart.cartAmount}">
<!-- 내가 클릭한 변경버튼의 해당하는 그 상품수량 숫자값을 들고오기위해 this 사용! -->
<button type="button" class="btn btn-light" th:onclick="goUpdateAmount(this);">변경</button>
</td>
<td><!-- (다른방법) [[${cart.itemVO.itemPrice}]]*[[${cart.cartAmount}]] -->
<div class="row">
<div class="col-6">
<div><span>[[${#numbers.formatCurrency(cart.itemPrice)}]]</span>*<span>[[${cart.cartAmount}]]</span> =</div><!-- 공백 표시 문자 기호 한칸띄어쓰기 -->
</div>
<div class="col-6">
<div class="totalPriceDiv" style="font-style: italic; font-weight: bold;"th:text="${#numbers.formatCurrency(cart.totalPrice)}" th:data-total-price="${cart.totalPrice}"> </div>
</div>
</div>
</td>
<td th:text="${cart.regDate}"> </td>
</tr>
</th:block>
</th:block>
</tbody>
</table>
</div>
</div>
<!-- 하단 영역 -->
<!-- 총가격 -->
<div class="row mt-5 mb-1" >
<div class="col-3"></div><!-- 줄맞춤용도 -->
<div class="col-3"></div><!-- 줄맞춤용도 -->
<div class="col-3"></div><!-- 줄맞춤용도 -->
<div class="col-3">
<div class="input-group mb-3 " >
<span class="input-group-text" id="inputGroup-sizing-default">
총가격
</span>
<div id="finalPriceTag" style="text-align: right;" th:data-total-price="${finalPrice}" th:text=" ${#numbers.formatCurrency(finalPrice)}" class="form-control" aria-label="Sizing example input" aria-describedby="inputGroup-sizing-default"></div>
</div>
</div>
</div>
<!-- 삭제 및 구매 버튼 -->
<div class="row mb-10" >
<div class="col" align="right">
<form action="insertBuys.buy" method="post">
<input type="hidden" name="totalPrice" id="totalPrice" value="">
<input type="hidden" name="itemCode" id="itemCode" value="">
<input type="hidden" name="cartAmount" id="cartAmount" value="">
<input type="hidden" name="cartCode" id="cartCode" value="">
<button onclick="goDelete();" class="btn btn-outline-danger">선택 삭제</button>
<button onclick="goBuy();" class="btn btn-outline-warning" style="color: #FF9F29;">선택 구매</button>
</form>
</div>
</div>
<!-- 수량변경 modal 창 -->
<div class="modal fade" id=updateAmountModal tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-body">
<div class="row mb-3">
<div class="col">
<div>상품 수량 변경을 하시겠습니까?</div>
</div>
</div>
<div class="row">
<div class="col text-end">
<!-- th:onclick="|location.href='@{컨트롤러경로}'|" -->
<!-- js에서 데이터 보내려면, 태그를 잘 선택해서 받아서 사용하기위해 data- 속성이용해서 보내기. -->
<button id="updateAmountBtn" th:onclick="updateAmount();" data-cart-code="" data-cart-amount="" type="button" class="btn btn-sm" style="--bs-btn-padding-y: .25rem; --bs-btn-padding-x: 3rem; --bs-btn-font-size: 1rem;">
확인
</button>
<button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal" style="--bs-btn-padding-y: .25rem; --bs-btn-padding-x: 3rem; --bs-btn-font-size: 1rem;">
취소
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="https://code.jquery.com/jquery-latest.min.js"></script>
<script type="text/javascript" th:src="@{/js/layout/cart_list.js}"></script>
</div>
</html>
✔ 단, Ajax사용시 다음과 같은 문제점이 발생한다.
1. 수량 변경버튼클릭 후, 변경은 하지만 가격영역에서 수량과 총가격은 페이지이동없이 변하지 않는다. 새로고침을 해야 변경된 값을 확인할 수 있다.
2. 마지막 finalPrice 값도 변하지 않기때문에 자동계산될 수 있도록 만들어야한다.
로그인 후, 해당 아이디 회원의 장바구니 페이지 이동시 첫 화면
여기서 수량을 새로 input태그에 입력 후, 변경 버튼 누르면 Modal창이 뜬다.
모달창의 확인 버튼을 누르면 '성공'이라는 alert창이 뜨면서 데이터베이스 update완료되는것을 알 수 있다.
취소버튼 클릭시, modal창이 닫히면서 이전 화면 그대로 나타난다.
하지만, Ajax를 사용해서 페이지이동(새로고침)을 하지않는다면, 수량영역 옆 가격영역에 표시되는 단가 * 수량 = 총가격의 데이터값과 오른쪽 하단의 총가격 데이터들이 자동으로 변경되지 않는다! 그래서 이 또한 Ajax로 넘겨받아 자동으로 계산되어 변경되도록 만들어줘야한다...(다음시간..)