5. 판매 게시글 페이지 - (2) 구현

2021. 1. 3. 16:59프로젝트/Salle(살래) 중고거래 웹

728x90

등록한 상품 정보를 전달하는 판매 게시글 페이지를 구현했습니다. 생각해보니 개별 상품 페이지는 등록된 상품들을 엮은 책에서 읽고 싶은 부분만 찾아 한번에 볼 수 있는 책갈피 기능이라, '책'(썸네일 페이지)을 먼저 만들어줘야 되겠더라구요. 그래서 메인페이지의 썸네일에 상품 id를 추가해 클릭 시 URI에 id가 붙도록 ProductList.jsp와 ProductInfoController를 구현해줬습니다. 

'책' == 썸네일 페이지

 

ProductInfoController - ProductList 코드

Controller에 Model 객체를 parameter로 추가해준 이유는 Service에서 등록된 상품 정보인 Product 객체를 List에 저장해 반환하는 SQL문을 실행하고 ProductList에 전달해줘야 ProductList.jsp의 화면에 보여줄 수 있기 때문입니다.

	@RequestMapping(value = "/productInfo/list", method = RequestMethod.GET)
	public String productListGet(Model model) {
		
		model.addAttribute("productList", productService.getProductList());
		
		return "productList";
	}
    

 

ProductList.jsp 코드 

<%@include file="" %> 태그는 웹사이트 header나 footer 파일을 호출할 수 있습니다. 코드 길이를 줄이고 유지보수에 편하단 장점이 있습니다. 유사한 태그로 <jsp:include page="">가 있는데요, include할 page가 javascript가 실행되는 등의 동적방식이라면 사용하시면 되구요. 저처럼 Navbar에서 로그인, 검색창, 카테고리 정도의 정적 page라면 전자인 <%@include... > 태그를 사용하시면 됩니다.

 

상품 썸네일 배치는 Product 객체를 반복해서 찍어주면 됩니다. <c: forEach var="" items=""> jstl C 태그를 사용했는데요, Java forEach문과 동일하게 items는 List/Array, var는 items 배열 내 인자를 받아줄 인스턴스명 입니다.

 

<body>

<%@include file="home.jsp" %>

	<article class="article_product">
	<c:forEach var="product" items="${productList}">
	<div class="container_product">
	<a href="<c:url value="/productInfo/${product.pr_id}"/>">
		<div class="div_pr_img">
			<img src="${product.pr_img_1}" class="pr_img"/></td>
		</div>
		<div class="div_pr_title">
			${product.pr_title}
		</div>	
		<div class="div_pr_price">
			${product.pr_price}원
		</div>
	</div>
	</a>
	</c:forEach>
	</article>

</body>

 

썸네일 페이지가 완성됐으니 이제 정말 판매 게시글 페이지를 구현해보자구요. SQL 처리는 Service에서 해주는 게 좋지만 가공을 거쳐야 할 데이터가 있어 Controller에 작성했습니다. DB의 다른 테이블에 저정된 판매자 nickName과 클라이언트가 요청한 시간으로부터 게시글 업로드 된 시간 차를 계산해 hours로 반환해 주는 hoursFromUpload 정보가 그것들입니다.

ProductInfoController - ProductInfo 코드

3부분으로 나뉘어 있습니다. 첫번째로 클릭한 상품 id의 모든 정보들을 DB로부터 Product 객체에 담아주는 getProductInfo(pr_id) 메서드, 두번째는 DB에서 상품 테이블이 아닌 회원 테이블에 저장된 nickName을 불러오기 위해 두 테이블을 SQL문으로 join시켜 해당 판매자 nickName을 받아오는 getMemberProductInfo(String email) 메서드. 마지막으로 업로드 후 클라이언트가 페이지를 요청한 현 시각까지 얼마만큼의 hours가 지났는지 반환해주는 getHoursFromUpload() 메서드 입니다. 

 

Controller 파라미터에 @Pathvariable은 URL에서 얻고 싶은 부분을 {변수명} 처리해 variable로 받을 수 있는 아노테이션 입니다. 저는 상품 id를 받아서 DB로부터 정보를 얻어올 것이므로 pr_id가 표시되는 부분을 {  } Curly braces 처리해주었습니다. 

	@RequestMapping(value = "/productInfo/{pr_id}", method = RequestMethod.GET)
	public String productInfoGet(Model model, @PathVariable int pr_id) {
		
		//.jsp에서 ${product.pr_title}
		Product productInfo = productService.getProductInfo(pr_id);
		model.addAttribute(productInfo);
		
		//member nickname
		model.addAttribute("nickName",productService.getMemberProductInfo(productInfo.getPr_email()));
		
		//hoursfromupload
	        Timestamp tsClient = Timestamp.valueOf(LocalDateTime.now());
	        long diffTime = tsClient.getTime() - productInfo.getPr_reg_date().getTime();
	        int hours = (int) (diffTime / (1000 * 3600));
	        productInfo.setHoursFromUpload(hours);
			model.addAttribute("hoursFromUpload",productInfo.getHoursFromUpload());
		
		return "productInfo";
	}

getMemberProductInfo - SQL문 코드

JOIN은 공통 Key가 존재하는 두 개 이상의 테이블이 있을 때 사용 가능하며 다른 테이블에 저장된 개체의 정보를 가져올 때 사용합니다. 저는 상품 테이블과 회원 테이블의 공통 Key인 email을 이용해 회원 테이블에서 nickName 데이터를 추출했습니다. 

 

LEFT OUTER JOIN은 기준 테이블(FROM에 명시된)의 정보와 둘 사이의 교집합 정보를 얻을 수 있는 JOIN방식입니다. 반대되는 방식으로는 RIGHT OUTER JOIN이 있고, 교집합 정보만 얻는 INNER JOIN과 모든 정보를 받아올 수 있는 FULL JOIN(= JOIN)이 있습니다.  

    	SELECT m.nickname
	    FROM member  AS m
	    LEFT OUTER JOIN product AS p
	    ON p.pr_email = m.email
	    WHERE m.email = #{pr_email}
	    LIMIT 1;

 

ProductInfo.jsp 코드

판매 게시글 페이지에서 가장 힘들었던 것은 View인 jsp파일을 구현하는 것이었습니다. 그 중 이미지 슬라이더를 만드는데서 스트레스를 많이 받았습니다... Back-end 부분은 막혀도 제 갈길이니 어쩔 수 없이 받아들이는 편인데, Fron-end를 하다 막혀버리면 코드 복붙으로 퉁치고 지나가면도 되지만, 최대한 원하는 방식으로 코드를 짜고 싶어서 두 사이의 딜레마에 빠지게 됩니다. 그러다 보면 스트레스는 불어나 프로젝트는 먹기 싫은 불어터진 라면처럼 변해버립니다. 

이미지 슬라이더는 이미지 파일 좌우측면 중간부분에 있는 화살표를 클릭하면 저장된 여러 개 상품이미지들이 보여지는 기능입니다. 유튜브 (참고영상) 전 이전 상품 등록하기 페이지에서 사용한 jQuery의 $().append() 메서드를 이용해 DB로부터 얻은 이미지 파일이 있을 경우 <div = "container_pr_img> 아래에 이미지 파일들을 <div ... style="background-image url()">에 담아  추가해줬습니다. (Javascript function imgAdd() 부분) 저도 압니다... 같은 코드들을 반복해서 친 비효율적인 언클린 코드라는 것을요. 다만 Product 객체를 Java EL문으로 받아줬어서 Javascript에서도 EL문을 사용하는데, 반복문 제어변수 i와 EL 그리고 태그 String을 혼용하다보니 인식하지 못하더라구요. 그래서 눈물을 머금과 switch문을 파줬습니다. 늘어난 메모리와 코드ㅠㅠ아직도 EL문은 문제입니다. .jsp에서 EL문 때문에 url()을 인식못해 화면에 출력되지 않는 이미지 파일들이 몇몇 있습니다.

function button_click(num)은 버튼 태그 <onclick>에 설정한 function입니다. 좌우를 클릭하면 각각 (-1, 1)이 현재 number인 currSlide 변수에 더해집니다. 그리고 현재 보여줄 이미지의 번호를 showSlide(); function에 넣어줍니다.

function ShowSlide(num)는 CSS background-image 기능을 이용해 <div>들을 배열에 담아 필요한 이미지만 표시하고 나머지는 숨겨줍니다. background-image는 경로에 등록한 이미지들을 같은 크기로 겹쳐서 화면에 보여줍니다. 보여지는 순서는 먼저 등록된 것부터 차례대로 입니다. 그리고 querySelectorAll()은 parameter에 해당되는 값들을 배열로 반환해주는 메서드입니다. 모든 이미지들을 for 반복문으로 display="none" 처리해 숨겨준 다음 현재 slides만 표시되도록 display="block" 처리해줍니다. 마지막으로 클릭하지 않아도 첫 화면에 첫번째 이미지 파일은 보여줘야 하기 때문에 showSlide(currSlide);를 function 밖에서 호출시킵니다. 

<body>

	<%@include file="home.jsp" %>
	
	<div class="container_pr_img">
		<a class="prev" onclick="button_click(-1)">&#10094</a>
		<a class="next" onclick="button_click(1)">&#10095</a>
	</div>
	
	<div class="container_info1">
		<div class="nickName">
			<a href="<c:url value="/profile/${nickName}"/>">
			${nickName}</a> 
		</div>
		<div class="pr_region">
			${product.pr_region} 
		</div>
	</div>


	<div class="container_info2">
			<div class="pr_title">
				${product.pr_title}
			</div>
			<div class="pr_category_reg_date">
				${product.pr_category}
				&nbsp;	
				${hoursFromUpload}시간 전
			</div>
			<div class="pr_price">
				${product.pr_price}원
			</div>
	</div>
	
	<p>
	<div class="container_info3">
		<div class="div_pr_detail">
			${product.pr_detail}
		</div>
	</div>
	</p>
		
	 <script>
	 	//div_pr_img 생성
	 	var currSlide = 1;
	 	
	 	$(document).ready(function imgAdd() {
	 		var i;
		 	for (i = 1; i < 6; i++) {
		 		var iStr = String(i);
		 		console.log(iStr);
		 		switch (i) {
					case 1:
	 					var divStr1 = "<div class='div_pr_img' id='pr_img_1' style='background-image: url(${product.pr_img_1}')></div>";
						if('${product.pr_img_1}' != '') {
							$(".container_pr_img").append(divStr1);
						 	showSlide(currSlide);
						}
						break;
					case 2:
	 					var divStr2 = "<div class='div_pr_img' id='pr_img_2' style='background-image: url(${product.pr_img_2}')></div>";
						if('${product.pr_img_2}' != '') {
							$(".container_pr_img").append(divStr2);
						}
						break;
					case 3:
	 					var divStr3 = "<div class='div_pr_img' id='pr_img_3' style=background-image: url(${product.pr_img_3})></div>";
						if('${product.pr_img_3}' != '') {
							$(".container_pr_img").append(divStr3);
						}
						break;
					case 4:
	 					var divStr4 = "<div class='div_pr_img' id='pr_img_4' style=background-image: url(${product.pr_img_4})></div>";
						if('${product.pr_img_4}' != '') {
							$(".container_pr_img").append(divStr4);
						}
						break;
					case 5:
	 					var divStr5 = "<div class='div_pr_img' id='pr_img_5' style=background-image: url(${product.pr_img_5})></div>";
						if('${product.pr_img_5}' != '') {
							$(".container_pr_img").append(divStr5);
						}
						break;
						}
		 		}
		 	});//end imgAdd()
		 	
		 	//이미지 슬라이더
            var currSlide = 1;
            showSlide(currSlide);
		 	
            function button_click(num) {
		 		showSlide((currSlide += num))
		 	}
		 	

		 	function showSlide(num) {
		 		const slides = document.querySelectorAll(".div_pr_img");
		 		if(num > slides.length) {
		 			currSlide = 1;
		 		} else if (num < 1) {
					currSlide = slides.length;
				}
		 		var i;
		 		for (i = 0; i < slides.length; i++) {
		 			slides[i].style.display = "none";			
		 		} 
		 		slides[currSlide - 1].style.display="block";
		 	}

		 	

		 	</script>

</body>