이 글을 적는 이유
저는 쇼핑몰 업무의 전문성을 가진 개발자가 되는게 목표입니다.
그래서 개인 포트폴리오도 쇼핑몰로 주제 선정하여 구현하게 되었습니다.
개인 쇼핑몰 프로젝트에서 제일 중요하고 개인적으로 어려웠던 부분은 장바구니 기능인 것 같습니다.
특히, 선택한 주문만 바로 금액이 합계가 되는 스크립트 부분과
선택한 상품만 DB에 보내는 두 개의 부분이 어려웠습니다.
관련 글을 참고하고자 인터넷검색으로 찾기도 너무 어려웠습니다. (아직 검색 능력이 부족한탓인가?)
이러한 장바구니 기능을 필요로 하는 개발자들에게 도움이 되기를 바라며,
나 스스로는 다음에 비슷한 기능을 더 능숙하게 다루기를...
+@
기존 글에는 cartList.jsp를 잘라서 보여드렸는데 스크립트의 부분적인 모습보다는 장바구니 전체 소스를 보여드리는편이 더 이해하기 좋을듯 하여 통으로 올렸습니다.
프론트를 bootstrap으로 만들었고 중간에 토큰값은 spring security을 적용해서 무시하셔도 무방합니다.
적용 모습_
흐름 설명
1. 처음 카트 리스트를 들어오면 리스트 전부 체크가 되어있습니다.
2. 상품 row에 체크박스를 checked를 풀면 체크되어있는 상품 금액만 합계가 되어 total에 나오게 되며
3. 주문하기를 누르면 체크되어있는 상품만 주문을 하게 되며 체크를 풀었던 상품은 장바구니에 그대로 남게 됩니다.
ShopController.class
// 카트 목록 불러오기
@GetMapping("/cartList")
public String getCartList(HttpSession session, Model model) throws Exception {
logger.info("get cart list");
//session에 저장해두었던 userId
String userId = (String)session.getAttribute("member");
// DB에 저장되어있던 cartList
List<CartListVO> cartList = service.cartList(userId);
model.addAttribute("cartList", cartList);
return "shop/cartList";
}
//카트리스트에서 주문하기
@PostMapping("/cartList")
public String order(HttpSession session, OrderVO order, @RequestParam(value = "chk[]") List<String> chArr) throws Exception {
logger.info("order");
String userId = (String)session.getAttribute("member");
//주문번호(orderId) 생성을 위한 로직
Calendar cal = Calendar.getInstance();
int year = cal.get(Calendar.YEAR);
String ym = year + new DecimalFormat("00").format(cal.get(Calendar.MONTH) + 1);
String ymd = ym + new DecimalFormat("00").format(cal.get(Calendar.DATE));
String subNum = "";
for(int i = 1; i <= 6; i ++) {
subNum += (int)(Math.random() * 10);
}
String orderId = ymd + "_" + subNum; //ex) 20200508_373063
order.setOrderId(orderId);
order.setUserId(userId);
service.orderInfo(order); //주문 테이블 insert
int cartNum = 0;
for(String i : chArr){
cartNum = Integer.parseInt(i);
System.out.println("cart -> CHK orderList : "+cartNum);
System.out.println("cart -> orderId orderList : "+orderId);
service.orderInfoDetails(orderId,cartNum); //주문 상세 테이블 insert
service.cartDelete(cartNum); //체크되어 들어온 cart번호로 cart table delete
}
return "redirect:/shop/myPage";
}
CartList.jsp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
|
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>BZshop | cartList</title>
<!-- bzshop Icon -->
<link rel=" shortcut icon" href="/images/bzshopicon.ico">
<link rel="icon" href="/images/bzshopicon.ico">
</head>
<body>
<!-- Page Preloder -->
<div id="preloder">
<div class="loader"></div>
</div>
<!-- Header Section Begin -->
<header class="header-section">
<div class="container-fluid">
<div class="inner-header">
<%@ include file="../shop/include/header.jsp" %>
</div>
</div>
</header>
<!-- Header End -->
<!-- Page Add Section Begin -->
<section class="page-add cart-page-add">
<div class="container">
<div class="row">
<div class="col-lg-6">
<div class="page-breadcrumb">
<h2>Cart<span>.</span></h2>
</div>
</div>
<c:if test="${not empty cartList}">
<div class="col-lg-5 offset-lg-1 text-left text-lg-right" style="padding-top: 34px;">
<div class="site-btn clear-btn" id="selectDelete_btn">선택된 리스트 삭제</div>
<script>
$("#selectDelete_btn").click(function () {
var confirm_val = confirm("정말 삭제하시겠습니까?");
if (confirm_val) {
var checkArr = new Array();
$("input[class='chkbox']:checked").each(function () {
checkArr.push($(this).attr("data-cartNum"));
});
$.ajax({
url: "/shop/deleteCart",
type: "post",
data: { chbox: checkArr },
beforeSend: function (xhr) { /*데이터를 전송하기 전에 헤더에 csrf값을 설정한다 (spring boot security 설정부분)*/
xhr.setRequestHeader("${_csrf.headerName}", "${_csrf.token}");
},
success: function () {
location.href = "/shop/cartList";
}
});
}
});
</script>
</div>
</c:if>
</div>
</div>
</section>
<!-- Page Add Section End -->
<!-- 금액 총 합계 -->
<script>
function itemSum() {
var str = "";
var sum = 0;
var count = $(".chkbox").length;
for (var i = 0; i < count; i++) {
if ($(".chkbox")[i].checked == true) {
sum += parseInt($(".chkbox")[i].value);
}
}
$("#total_sum").html(sum + " 원");
$("#amount").val(sum);
}
</script>
<!-- Cart Page Section Begin -->
<div class="cart-page">
<div class="container">
<div class="cart-table">
<table>
<thead>
<tr>
<th><input type="checkbox" name="allCheck" id="allCheck" checked /></th>
<!-- 상품 전체선택 -->
<script>
$("#allCheck").click(function () {
var chk = $("#allCheck").prop("checked");
if (chk) {
$(".chkbox").prop("checked", true);
itemSum();
} else {
$(".chkbox").prop("checked", false);
itemSum();
}
});
</script>
<th class="product-h">Product</th>
<th>Price</th>
<th>Quantity</th>
<th>Total</th>
<th></th>
</tr>
</thead>
<tbody>
<c:forEach items="${cartList}" var="cartList">
<tr>
<td class="product-close"><input type="checkbox" onClick="itemSum()"
class="chkbox" value="${cartList.gdsPrice * cartList.cartStock}"
data-cartNum="${cartList.cartNum}" /></td>
<td class="product-col">
<img src="${cartList.gdsThumbImg}" alt="${cartList.gdsThumbImg}" />
<div class="p-title">
<h5><a
href="/shop/view?n=${cartList.gdsNum}">${cartList.gdsName}</a>
</h5>
</div>
</td>
<td class="price-col">
<fmt:formatNumber pattern="###,###,###" value="${cartList.gdsPrice}" />
원
</td>
<td>
${cartList.cartStock} 개
</td>
<td class="total">
<fmt:formatNumber pattern="###,###,###"
value="${cartList.gdsPrice * cartList.cartStock}" /> 원
</td>
<td class="product-close" id="delete_${cartList.cartNum}_btn"
data-cartNum="${cartList.cartNum}">x</td>
<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 },
beforeSend: function (xhr) { /*데이터를 전송하기 전에 헤더에 csrf값을 설정한다 (spring boot security 설정부분)*/
xhr.setRequestHeader("${_csrf.headerName}", "${_csrf.token}");
},
success: function (result) {
if (result == 1) {
location.href = "/shop/cartList";
} else {
alert("삭제 실패");
}
}
});
}
});
</script>
</tr>
</c:forEach>
</tbody>
</table>
<!-- 초기화면 상품 전체선택이지만 하나라도 체크박스 해제할 경우 이벤트 -->
<script>
$(".chkbox").click(function () {
$("#allCheck").prop("checked", false);
});
</script>
<c:if test="${empty cartList}">
<c:set var="cart" value="false" />
<div class="card border-light mb-3 text-center spad">
<div class="card-header">
<h3>카트에 상품이 없습니다.</h3>
</div>
<div class="card-body">
<p class="card-text">카트에 물건을 담고 이용해주세요!</p>
</div>
</div>
</c:if>
</div>
</div>
<div class="shopping-method">
<div class="container">
<div class="row">
<div class="col-lg-12">
<div class="total-info">
<div class="total-table">
<table>
<thead>
<tr>
<th class="total-cart">Total Cart</th>
</tr>
</thead>
<tbody>
<tr>
<td class="total-cart-p" id="total_sum"></td>
</tr>
</tbody>
</table>
</div>
<c:if test="${not empty cartList}">
<div class="row">
<div class="col-lg-12 text-right">
<button type="button" class="primary-btn chechout-btn"
id="orderModal" data-toggle="modal" data-target="#orderOpne">주문
하기</button>
</div>
</div>
</c:if>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Cart Page Section End -->
<!-- Footer Section Begin -->
<footer class="footer-section spad">
<%@ include file="../shop/include/footer.jsp" %>
</footer>
<!-- Footer Section End -->
<!--order Modal Begin -->
<div class="modal fade" id="orderOpne" data-backdrop="static" tabindex="-1" role="dialog"
aria-labelledby="staticBackdropLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="staticBackdropLabel">주문지 작성</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<form action="/shop/cartList" method="post" autocomplete="off" id="orderForm">
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
<input type="hidden" name="amount" id="amount" value="" />
<input type="hidden" name="chk[]" id="chk" value="" />
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text" id="basic-addon1">수령인 성함</span>
</div>
<input type="text" name="orderRec" id="orderRec" class="form-control"
required="required" />
</div>
<div class="input-group mb-3">
<div class="input-group-prepend">
<span class="input-group-text" id="basic-addon1">수령인 전화번호</span>
</div>
<input type="text" name="orderPhon" id="orderPhon" class="form-control"
required="required" />
</div>
<label>수령 주소</label>
<div class="input-group mb-3">
<input type="text" id="sample3_postcode" class="form-control" placeholder="우편번호"
readonly>
<div class="input-group-append">
<input type="button" class="btn btn-secondary" id="button-addon2"
onclick="sample3_execDaumPostcode()" value="우편번호 찾기">
</div>
</div>
<div class="input-group mb-3">
<input type="text" name="userAddr1" id="sample3_address" placeholder="주소1"
style="width:50%;" readonly>
<input type="text" name="userAddr3" id="sample3_extraAddress" placeholder="주소2"
style="width:50%;" readonly>
<input type="text" name="userAddr2" id="sample3_detailAddress"
class="form-control" placeholder="상세주소" style="width:100%;">
<!-- iOS에서는 position:fixed 버그가 있음, 적용하는 사이트에 맞게 position:absolute 등을 이용하여 top,left값 조정 필요 -->
<div id="wrap"
style="display:none;border:1px solid;width:500px;height:300px;margin:5px 0;position:relative">
<img src="//t1.daumcdn.net/postcode/resource/images/close.png"
id="btnFoldWrap"
style="cursor:pointer;position:absolute;right:0px;top:-1px;z-index:1"
onclick="foldDaumPostcode()" alt="접기 버튼">
</div>
<!-- 주소지 찾기 -->
<script
src="https://t1.daumcdn.net/mapjsapi/bundle/postcode/prod/postcode.v2.js"></script>
<script>
// 우편번호 찾기 찾기 화면을 넣을 element
var element_wrap = document.getElementById('wrap');
function foldDaumPostcode() {
// iframe을 넣은 element를 안보이게 한다.
element_wrap.style.display = 'none';
}
function sample3_execDaumPostcode() {
// 현재 scroll 위치를 저장해놓는다.
var currentScroll = Math.max(document.body.scrollTop, document.documentElement.scrollTop);
new daum.Postcode({
oncomplete: function (data) {
// 검색결과 항목을 클릭했을때 실행할 코드를 작성하는 부분.
// 각 주소의 노출 규칙에 따라 주소를 조합한다.
// 내려오는 변수가 값이 없는 경우엔 공백('')값을 가지므로, 이를 참고하여 분기 한다.
var addr = ''; // 주소 변수
var extraAddr = ''; // 참고항목 변수
//사용자가 선택한 주소 타입에 따라 해당 주소 값을 가져온다.
if (data.userSelectedType === 'R') { // 사용자가 도로명 주소를 선택했을 경우
addr = data.roadAddress;
} else { // 사용자가 지번 주소를 선택했을 경우(J)
addr = data.jibunAddress;
}
// 사용자가 선택한 주소가 도로명 타입일때 참고항목을 조합한다.
if (data.userSelectedType === 'R') {
// 법정동명이 있을 경우 추가한다. (법정리는 제외)
// 법정동의 경우 마지막 문자가 "동/로/가"로 끝난다.
if (data.bname !== '' && /[동|로|가]$/g.test(data.bname)) {
extraAddr += data.bname;
}
// 건물명이 있고, 공동주택일 경우 추가한다.
if (data.buildingName !== '' && data.apartment === 'Y') {
extraAddr += (extraAddr !== '' ? ', ' + data.buildingName : data.buildingName);
}
// 표시할 참고항목이 있을 경우, 괄호까지 추가한 최종 문자열을 만든다.
if (extraAddr !== '') {
extraAddr = ' (' + extraAddr + ')';
}
// 조합된 참고항목을 해당 필드에 넣는다.
document.getElementById("sample3_extraAddress").value = extraAddr;
} else {
document.getElementById("sample3_extraAddress").value = '';
}
// 우편번호와 주소 정보를 해당 필드에 넣는다.
document.getElementById('sample3_postcode').value = data.zonecode;
document.getElementById("sample3_address").value = addr;
// 커서를 상세주소 필드로 이동한다.
document.getElementById("sample3_detailAddress").focus();
// iframe을 넣은 element를 안보이게 한다.
// (autoClose:false 기능을 이용한다면, 아래 코드를 제거해야 화면에서 사라지지 않는다.)
element_wrap.style.display = 'none';
// 우편번호 찾기 화면이 보이기 이전으로 scroll 위치를 되돌린다.
document.body.scrollTop = currentScroll;
},
// 우편번호 찾기 화면 크기가 조정되었을때 실행할 코드를 작성하는 부분. iframe을 넣은 element의 높이값을 조정한다.
onresize: function (size) {
element_wrap.style.height = size.height + 'px';
},
width: '100%',
height: '100%'
}).embed(element_wrap);
// iframe을 넣은 element를 보이게 한다.
element_wrap.style.display = 'block';
}
</script>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">취소</button>
<button type="button" class="btn btn-primary" id="orderSuccess">주문 완료</button>
<!-- 주문 완료 이벤트 -->
<script>
$("#orderSuccess").click(function () {
var checkArr = new Array();
$("input[class='chkbox']:checked").each(function () {
checkArr.push($(this).attr("data-cartNum"));
});
console.log(checkArr);
$("#chk").val(checkArr);
var nickNameCheck = /^[가-힣]{2,6}$/;
var phonNumberCheck = /^\d{3}-\d{3,4}-\d{4}$/;
if (!nickNameCheck.test($("#orderRec").val())) {
alert("2~6글자의 한글만 입력해주세요.");
$('#orderRec').val("").focus();
} else if (!phonNumberCheck.test($('#orderPhon').val())) {
alert("010-xxx-xxxx or 010-xxxx-xxxx");
$('#orderPhon').val("").focus();
} else if (confirm("주문완료 하시겠습니까?")) {
alert("주문 감사합니다.");
$("#orderForm").submit();
}
});
</script>
</form>
</div>
</div>
</div>
</div>
<!--order Modal End -->
<!-- 페이지 들어오자마자 체크박스 체크 -->
<script>
var tt = "${cart}";
if (tt == 'false') {
$("#allCheck").prop("checked", false);
} else {
$("#allCheck").prop("checked", true);
$(".chkbox").prop("checked", true);
itemSum();
}
</script>
<!-- 주문하기 버튼 이벤트 -->
<script>
$("#orderModal").click(function () {
$.ajax({
url: "/shop/orderUser/",
success: function (data) {
$("#orderRec").val(data.userName);
$("#orderPhon").val(data.userPhon);
$("#sample3_address").val(data.userAddr1);
$("#sample3_detailAddress").val(data.userAddr2);
$("#sample3_extraAddress").val(data.userAddr3);
}
});
});
</script>
</body>
</html>
|
cs |
cartList.jsp의 스크립트 위치가 중요하다는걸 알고계시면 커스텀이 더 편할듯 합니다.
필요한 부분만 잘라가시면 될 것 같습니다.