[Spring][쇼핑몰 프로젝트][41] 주문 구현(주문 페이지) - 1
프로젝트 Github : https://github.com/sjinjin7/Blog_Project
프로젝트 포스팅 색인(index) : https://kimvampa.tistory.com/188
목표
주문 구현 - 페이지 구현
사용자가 주문을 할 수 있는 페이지를 구현하는 것이 목표입니다. 이번 포스팅에선 주문 페이지에 데이터를 넘기는 것을 목표로 합니다.
순서
1. 주문 기능 개요
2. DTO 클래스 생성
3. OrderController.java
4. 뷰 처리
4.1 goodsDetail.jsp
4.2 cart.jsp
5. 테스트
1. 주문 페이지 개요
주문 페이지는 회원이 상품을 주문하기 위한 페이지 입니다. 주문 페이지 이동을 위해선 두 가지 루트가 있는데, 하나는 회원의 장바구니 페이지에서 주문을 하기 위한 물품을 선택 후 '구매' 버튼을 눌렀을 때 이동이 가능합니다. 다른 하나는 상품 검색 후 선택한 '상품 상세페이지'에 '바로 구매' 버튼을 눌렀을 때입니다.
주문 페이지 구현을 위해 크게 두 범주로 나누어서 생각 해보았습니다.
첫 번째, 회원이 선택한 상품 데이터를 어떻게 '주문 페이지'로 이동 시킬 것인가입니다.
두 번째, '주문 페이지'의 구성은 어떻게 할지입니다.
1.1 주문 페이지 데이터 넘기기
주문 페이지에는 상품을 주문하는 회원에 대한 정보와 주문하고자 하는 상품의 정보를 전달해야 합니다. 그렇다고 이미 만들어진 모든 정보를 장바구니 페이지, 상품 상세 페이지에서 일일이 넘길 필요는 없고 '상품'과 '회원'의 기본키인 memberId, bookId와 회원이 얼마만큼의 상품을 주문하는지에 대한 정보인 bookCount 이 3가지 정보를 서버에 전달시켜서 필요한 데이터를 만들어 낸 후 '주문 페이지'로 넘기면 됩니다.
그런데 한 가지 고려해보아야 할 점은 여러 개의 상품 데이터(bookId, bookCount)를 어떻게 서버에 전달할 것인가 입니다. '상품 상세 페이지' 에서 '바로 구매' 버튼을 누르게 된다면 Controller 의 URL 매핑 메서드의 파라미터에 'bookId', 'bookCount', 'memberId' 3개의 변수가 담긴 클래스를 선언 하기만 하면 됩니다.
하지만 두 개 이상의 상품 정보를 클라이언트에서 서버로 넘기기 위해선 3개의 변수만 선언된 객체로는 넘길 수가 없습니다.
여러 개의 데이터를 전달 할 수 있도록 하기 위해서 bookId, bookCount만 담는 클래스를 만든 후, 이 클래스를 요소로 가지는 List 객체 타입 변수로 가지는 클래스를 URL 매핑 메서드의 파라미터로 선언할 것입니다.
서버에서 데이터를 전달받을 그릇은 정했는데 그렇다면 뷰페이지에서 데이터를 전송하기 위한 <input> 태그의 name을 어떻게 해야 할 것인지를 고민 해야합니다. <input>태그의 name은 배열 접근하는 방식으로 지정하면 됩니다. (말은 어렵게 하였지만 코드를 직접 보신다면 이해가 갈 것입니다.)
1.2 주문 페이지 구성
주문 페이지에서는 우리가 앞선 포스팅에서 생성한 테이블에 입력할 값들을 가지고 있거나 입력할 수 있어야 합니다. 전체적인 큰 틀은 교보문고의 주문 페이지를 참고하여 진행하겠습니다.
주요 구성은 '회원 정보', '배송 정보(주소지, 받는 이)', '상품 정보', '사용 포인트'입니다. 페이지에 대한 구현은 다음 포스팅에서 자세히 진행하겠습니다.
2. DTO 클래스 생성
먼저 뷰에서 전송한 데이터를 전달받을 객체가 될 클래스를 생성하겠습니다.
OrderPageItemDTO
먼저 상품 데이터를 담을 클래스입니다.
com.vam.model 패키지에 OrderPgaeItemDTO 클래스를 생성합니다.
뷰로부터 bookId, bookCount 데이터를 전달받기 위해서 아래 두 개의 변수를 선언합니다.
/* 뷰로부터 전달받을 값 */
private int bookId;
private int bookCount;
이 클래스는 뷰에서 서버로 데이터를 전달하는 역할뿐만 아니라 서버에서 뷰로 상품 관련 데이터를 담을 그릇 역할 또한 수행하도록 만들 것입니다. bookId를 통해 DB에서 꺼내올 아래 3개의 데이터를 담을 변수를 선언해줍니다.
/* 뷰로부터 전달받을 값 */
private int bookId;
private int bookCount;
/* DB로부터 꺼내올 값 */
private String bookName;
private int bookPrice;
private double bookDiscount;
장바구니 페이지 때와 마찬가지로 '상품의 가격', '총 가격', '한 개의 상품 구매로 받을 수 있는 포인트', '총 포인트' 이 데이터들을 미리 만들어서 뷰로 보내줄 것입니다. 따라서 앞서 말한 데이터들을 담을 수 있는 변수 4개를 선언합니다.
/* 뷰로부터 전달받을 값 */
private int bookId;
private int bookCount;
/* DB로부터 꺼내올 값 */
private String bookName;
private int bookPrice;
private double bookDiscount;
/* 만들어 낼 값 */
private int salePrice;
private int totalPrice;
private int point;
private int totalPoint;
선언한 변수들을 접근할 수 있도록 getter/setter를 작성합니다.
public int getBookId() {
return bookId;
}
public void setBookId(int bookId) {
this.bookId = bookId;
}
public int getBookCount() {
return bookCount;
}
public void setBookCount(int bookCount) {
this.bookCount = bookCount;
}
public String getBookName() {
return bookName;
}
public void setBookName(String bookName) {
this.bookName = bookName;
}
public int getBookPrice() {
return bookPrice;
}
public void setBookPrice(int bookPrice) {
this.bookPrice = bookPrice;
}
public double getBookDiscount() {
return bookDiscount;
}
public void setBookDiscount(double bookDiscount) {
this.bookDiscount = bookDiscount;
}
public int getSalePrice() {
return salePrice;
}
public void setSalePrice(int salePrice) {
this.salePrice = salePrice;
}
public int getTotalPrice() {
return totalPrice;
}
public void setTotalPrice(int totalPrice) {
this.totalPrice = totalPrice;
}
public int getPoint() {
return point;
}
public void setPoint(int point) {
this.point = point;
}
public int getTotalPoint() {
return totalPoint;
}
public void setTotalPoint(int totalPoint) {
this.totalPoint = totalPoint;
}
salePrice, totalPrice, point, totalPoint의 경우 DB에서 꺼내올 수 있는 데이터가 아니라 bookPrice, bookCount, bookDiscount 값들을 통해서 만들어 낼 수 있는 변수들이기 때문에 이 변수들의 값을 세팅할 수 있는 메서드를 만들어줍니다.
public void initSaleTotal() {
this.salePrice = (int) (this.bookPrice * (1-this.bookDiscount));
this.totalPrice = this.salePrice*this.bookCount;
this.point = (int)(Math.floor(this.salePrice*0.05));
this.totalPoint =this.point * this.bookCount;
}
toString 메서드 또한 추가해주었습니다.
@Override
public String toString() {
return "OrderPageItemDTO [bookId=" + bookId + ", bookCount=" + bookCount + ", bookName=" + bookName
+ ", bookPrice=" + bookPrice + ", bookDiscount=" + bookDiscount + ", salePrice=" + salePrice
+ ", totalPrice=" + totalPrice + ", point=" + point + ", totalPoint=" + totalPoint + "]";
}
OrderPageDTO
앞서 선언한 클래스의 객체를 요소로 가지는 List 타입의 변수를 가지는 클래스를 작성해보겠습니다.
com.vam.model 패키지에 OrderPageDTO 클래스를 생성합니다.
앞서 선언한 클래스를 generic 타입으로 가지는 List 타입의 변수를 선언해줍니다.
private List<OrderPageItemDTO> orders;
선언한 변수의 getter/setter 메서드와 toString 메서드를 선언해줍니다.
public List<OrderPageItemDTO> getOrders() {
return orders;
}
public void setOrders(List<OrderPageItemDTO> orders) {
this.orders = orders;
}
@Override
public String toString() {
return "OrderPageDTO [orders=" + orders + "]";
}
3. OrderController.java
'주문 페이지' 이동을 위한 URL매핑 메서드를 추가해주겠습니다.
먼저 com.vam.controller 패키지에 OrderController.java 클래스를 생성합니다.
클래스 선언부에 @Contorller 어노테이션을 추가해줍니다.
'주문 페이지' 이동을 수행하는 URL 매핑 메소드를 추가합니다.
@GetMapping("/order/{memberId}")
public void orderPgaeGET(@PathVariable("memberId") String memberId, OrderPageDTO opd, Model model) {
System.out.println("memberId : " + memberId);
System.out.println("orders : " + opd.getOrders());
}
- '주문 페이지' 이동을 수행하는 URL은 PathVariable 스타일("/order/" + memberId )로 설계하였습니다.
- 이번 포스팅에서는 데이터가 전달되는지를 확인하는 것이 목적이기 때문에 리턴 타입은 void로 하였습니다.
- 파라미터에는 URL을 통해 전달받는 memberId를 파라미터 변수로 선언하였고, 상품 정보를 전달받을 수 있도록 OrderPageDTO 타입의 변수를 선언하였습니다. 그리고 이 메서드에서 만들어낸 정보를 뷰로 전달하기 위해서 Mdoel 타입의 파라미터 변수를 추가해주었습니다.
- println() 메서드를 사용하여 console. 창에 전달받은 데이터가 출력되도록 코드를 작성하였습니다.
4. 뷰 처리
4.1 goodsDetail.jsp
태그 추가
godosDetail.jsp 파일에 <body> 태그 내부 중 아무 곳에 '장바구니 페이지'로 데이터를 전송하기 위한 <form> 태그와 <input> 태그를 추가해줍니다.
<!-- 주문 form -->
<form action="/order/${member.memberId}" method="get" class="order_form">
<input type="hidden" name="orders[0].bookId" value="${goodsInfo.bookId}">
<input type="hidden" name="orders[0].bookCount" value="">
</form>
- name을 보시면 OrderPageDTO 클래스에 선언한 List 타입의 orders 변수의 첫 번째 요소에 담기도록 index가 작성된 것을 확인할 수 있습니다. 요소(OrderItemPageDTO)의 변수에 접근하기 위해 ". 변수명"을 사용하였습니다.
- bookCount <input> 태그의 value 값은 일단 비워 주었습니다. 수량은 사용자가 '바로 구매' 버튼을 눌렀을 때 확정이 되기 때문에 Js 코드를 통해서 동적으로 추가되도록 해 줄 것입니다.
javascript 코드
사용자가 '바로 구매' 버튼을 눌렀을때 페이지 이동 기능이 수행되도록 Javascript 코드를 추가해주겠습니다. <script> 태그 내부에 '바로 구매' 버튼을 눌렀을 때 동작하는 메서드를 추가해줍니다.
/* 바로구매 버튼 */
$(".btn_buy").on("click", function(){
});
bookCount 변수를 선언하고 사용자가 수량 <input>을 통해 확정된 수량 값을 저장해줍니다. bookCount 값을 bookCount <input> 태그의 값으로 부여 해준 후 <form>을 서버로 전송해주는 코드를 추가합니다.
/* 바로구매 버튼 */
$(".btn_buy").on("click", function(){
let bookCount = $(".quantity_input").val();
$(".order_form").find("input[name='orders[0].bookCount']").val(bookCount);
$(".order_form").submit();
});
4.2 cart.jsp
장바구니 페이지에서는 체크박스를 통해 상품을 선택할 수 있도록 하였기 때문에, 회원이 상품을 선택 후 '주문 하기' 버튼을 클릭했을 때 어떠한 상품을 주문했는지를 알 수 있습니다. 따라서 장바구니 페이지에서 상품 관련 <input> 태그는 회원이 '주문 하기' 버튼을 클릭했을 때 선택한 물품들에 관한 <input> 태그가 동적으로 추가되도록 할 것입니다.
태그 추가
상품 정보 <input>태그가 담긴 <td> 태그에 bookId <input> 태그를 추가해줍니다.
<input type="hidden" class="individual_bookId_input" value="${ci.bookId}">
<form> 태그가 작성된 곳 바로 아래에 주문 페이지 <form> 태그를 추가해줍니다. <input>는 동적으로 추가해 줄 것이기 때문에 비워둡니다.
<!-- 주문 form -->
<form action="/order/${member.memberId}" method="get" class="order_form">
</form>
'주문하기' 버튼 태그에 쉽게 접근하기 위해서 class 속성을 추가하였습니다.
<a class="order_btn">주문하기</a>
Javascript 코드
회원이 '주문하기' 버튼을 눌렀을 때 동작하는 메서드를 <script> 태그 내부에 작성해줍니다. 동적으로 생성할 <input> 태그 문자열 값이 저장될 form_contents 변수를 선언하고 빈 문자열 값으로 초기화합니다.
/* 주문 페이지 이동 */
$(".order_btn").on("click", function(){
let form_contents ='';
});
<input>의 name값에 orders[0], orders[1], orders[2] 와 값이 index 값을 주어야 하는데 이러한 index값 역할을 할 orderNumber 변수를 선언하고 0으로 초기화합니다.
/* 주문 페이지 이동 */
$(".order_btn").on("click", function(){
let form_contents ='';
let orderNumber = 0;
});
상품의 데이터가 저장된 <input> 값들을 감싸고 있는 <td> 태그 반복해서 접근하는 메서드를 추가합니다.
/* 주문 페이지 이동 */
$(".order_btn").on("click", function(){
let form_contents ='';
let orderNumber = 0;
$(".cart_info_td").each(function(index, element){
});
});
bookId, bookCount변수를 선언하여 접근한 <td>태그 내부에 있는 <input> 태그의 값들로 초기화해줍니다.
/* 주문 페이지 이동 */
$(".order_btn").on("click", function(){
let form_contents ='';
let orderNumber = 0;
$(".cart_info_td").each(function(index, element){
let bookId = $(element).find(".individual_bookId_input").val();
let bookCount = $(element).find(".individual_bookCount_input").val();
});
});
두 변수의 값과 index(each 메서드 첫 번째 인자) 값을 활용해서 서버로 전송되어야 할 <input> 태그를 문자열 값을 만들어내고 앞서 선언한 form_contents 변수에 문자열을 더해줍니다.
/* 주문 페이지 이동 */
$(".order_btn").on("click", function(){
let form_contents ='';
let orderNumber = 0;
$(".cart_info_td").each(function(index, element){
let bookId = $(element).find(".individual_bookId_input").val();
let bookCount = $(element).find(".individual_bookCount_input").val();
let bookId_input = "<input name='orders[" + orderNumber + "].bookId' type='hidden' value='" + bookId + "'>";
form_contents += bookId_input;
let bookCount_input = "<input name='orders[" + orderNumber + "].bookCount' type='hidden' value='" + bookCount + "'>";
form_contents += bookCount_input;
});
});
그리고 idnex값 역할을 하는 orderNumber는 다음 객체에 접근할 때 +1이 될 수 있도록 코드를 추가합니다.
/* 주문 페이지 이동 */
$(".order_btn").on("click", function(){
let form_contents ='';
let orderNumber = 0;
$(".cart_info_td").each(function(index, element){
let bookId = $(element).find(".individual_bookId_input").val();
let bookCount = $(element).find(".individual_bookCount_input").val();
let bookId_input = "<input name='orders[" + orderNumber + "].bookId' type='hidden' value='" + bookId + "'>";
form_contents += bookId_input;
let bookCount_input = "<input name='orders[" + orderNumber + "].bookCount' type='hidden' value='" + bookCount + "'>";
form_contents += bookCount_input;
orderNumber += 1;
});
});
모든 상품 정보를 <input> 태그로 만드는 것이 아니라 사용자가 체크한 상품만 <input>태그로 만들어야 하기 때문에 if문으로 감싸줍니다. 조건문으로는 체크박스가 체크가 되어 있으면 1을 반환하는 코드를 작성하였습니다. (이전 장바구니 페이지 포스팅 참고)
/* 주문 페이지 이동 */
$(".order_btn").on("click", function(){
let form_contents ='';
let orderNumber = 0;
$(".cart_info_td").each(function(index, element){
if($(element).find(".individual_cart_checkbox").is(":checked") === true){ //체크여부
let bookId = $(element).find(".individual_bookId_input").val();
let bookCount = $(element).find(".individual_bookCount_input").val();
let bookId_input = "<input name='orders[" + orderNumber + "].bookId' type='hidden' value='" + bookId + "'>";
form_contents += bookId_input;
let bookCount_input = "<input name='orders[" + orderNumber + "].bookCount' type='hidden' value='" + bookCount + "'>";
form_contents += bookCount_input;
orderNumber += 1;
}
});
});
each 메서드를 벗어나서 form_contents 변수에 저장된 문자열(<input> 태그) 값을 주문 페이지 이동 <form> 태그 내부에 추가되도록 코드를 추가하고, <form> 태그를 서버로 전송합니다.
/* 주문 페이지 이동 */
$(".order_btn").on("click", function(){
let form_contents ='';
let orderNumber = 0;
$(".cart_info_td").each(function(index, element){
if($(element).find(".individual_cart_checkbox").is(":checked") === true){ //체크여부
let bookId = $(element).find(".individual_bookId_input").val();
let bookCount = $(element).find(".individual_bookCount_input").val();
let bookId_input = "<input name='orders[" + orderNumber + "].bookId' type='hidden' value='" + bookId + "'>";
form_contents += bookId_input;
let bookCount_input = "<input name='orders[" + orderNumber + "].bookCount' type='hidden' value='" + bookCount + "'>";
form_contents += bookCount_input;
orderNumber += 1;
}
});
$(".order_form").html(form_contents);
$(".order_form").submit();
});
5. 테스트
서버를 구동시켜서 '장바구니 페이지', '상품 상세 페이지'의 '주문' 버튼을 눌러서 데이터가 서버에 정상적으로 전달되었는지를 확인합니다.
REFERENCE
DATE
- 2020.12.08