[싱글이의 영수증] 목록 조회
업데이트:
싱글이의 영수증 목록 조회
한 페이지에서 9개의 게시글을 볼 수 있으며, 그 이상의 게시글은 페이징바를 이용해서 페이지를 이동해서 볼 수 있게 구현했다.
- 출력화면
Controller
컨트롤러에서 /review/로 시작되는 모든 요청을 전달받는다.
package com.gaji.SingleBungle.review.controller;
@Controller
@SessionAttributes({"loginMember"})
@RequestMapping("/review/*")
public class ReviewController {
@Autowired
private ReviewService service;
private String swalIcon = null;
private String swalTitle = null;
private String swalText = null;
페이징 처리를 위한 cp와 회원 정보를 얻어올 loginMember, 그리고 데이터를 전달할 Model 객체, alert창을 전달할 redirectAttributes 객체를 매개변수로 얻어온다.
페이징 처리를 위해 파라미터 cp를 얻어올 때 등록된 값이 없다면 1을 출력할 수 있도록 defaultValue 를 1로 지정해준다.
@RequestMapping("list")
public String reviewList(@RequestParam(value="cp", required=false, defaultValue="1") int cp,
@ModelAttribute(name="loginMember", binding=false) Member loginMember, Model model,
RedirectAttributes ra) {
2등급 이상이 아닌 경우 alert창이 나타나며 메인 페이지로 돌아간다.
String url = null;
if(loginMember.getMemberGrade().charAt(0) == 'T') {
swalIcon = "error";
swalTitle ="'싱글이의 영수증'게시판은 2등급부터 이용할 수 있습니다.";
url = "redirect:/";
}else {
// 페이징 처리
ReviewPageInfo pInfo = service.getPageInfo(cp);
//게시글 목록 조회
List<Review> rList = service.selectList(pInfo);
/* 썸네일 출력 */
if(rList!=null && !rList.isEmpty()) {
List<ReviewAttachment> thumbnailList = service.selectThumbnailList(rList);
if(thumbnailList!=null) {
model.addAttribute("thList", thumbnailList);
}
}
List<ReviewLike> likeInfo = service.selectLike(loginMember.getMemberNo());
model.addAttribute("likeInfo", likeInfo);
model.addAttribute("rList", rList);
model.addAttribute("pInfo",pInfo);
url = "review/reviewList";
}
ra.addFlashAttribute("swalIcon", swalIcon);
ra.addFlashAttribute("swalTitle", swalTitle);
return url;
}
- 페이징 처리
현재 페이지 번호와, 전체 게시글 수를 조회해 reviewPageInfo객체를 생성합니다.
Service
ReviewPageInfo getPageInfo(int cp);
ServcieImpl
@Override
public ReviewPageInfo getPageInfo(int cp) {
int listCount = dao.getListCount();
return new ReviewPageInfo(cp, listCount, 2);
}
DAO
public int getListCount() {
return sqlSession.selectOne("reviewMapper.getListCount");
}
mapper
<select id="getListCount" resultType="_int">
SELECT COUNT(*) FROM
V_BOARD
WHERE STATUS = 'N'
AND BOARD_CD = '2'
</select>
- 게시글 목록 조회
RowBounds객체를 이용해 조회해 온 결과 중 일부 행만 반환해준다.
(RowBounds offset,pInfo.getLimit())
첫번째 데이터에서 몇 개의 데이터가 지났는지, 몇 개를 보여줄지
offset = (현재페이지 -1) * 한 페이지에 조회되는 게시글 수
= (5-1)= 4 * 9 = 36
1(1~9) 2(10~18) 3(19~27) 4(28~36) 5(37~45)
service
List<Review> selectList(ReviewPageInfo pInfo);
serviceImpl
@Override
public List<Review> selectList(ReviewPageInfo pInfo) {
return dao.selcetList(pInfo);
}
DAO
public List<Review> selcetList(ReviewPageInfo pInfo) {
// RowBounds 객체 : offset과 limit를 이용하여 조회 결과 중 일부 행만 조회하는
// 마이바티스 객체
int offset = (pInfo.getCurrentPage()-1) * pInfo.getLimit();
RowBounds rowBounds = new RowBounds(offset , pInfo.getLimit());
return sqlSession.selectList("reviewMapper.selectList", pInfo, rowBounds);
}
mapper
<select id="selectList" parameterType="_int" resultMap="review_rm">
SELECT BOARD_NO,BOARD_TITLE, READ_COUNT, CATEGORY_CD,
CATEGORY_NM, NICKNAME, CREATE_DT, LIKE_COUNT
FROM V_BOARD
WHERE STATUS = 'N'
AND
BOARD_CD = '2'
ORDER BY BOARD_NO DESC
</select>
- 썸네일 목록 조회
게시글 목록 조회에 성공했을 때 썸네일 목록을 조회한다.
썸네일 목록은 파일 레벨 중 1인 것들 중에,
조회가 성공한 게시글 정보를 매개변수로 받아
동적sql을 수행해서 게시글 번호가 일치하는 것만 가져온다.
service
List<ReviewAttachment> selectThumbnailList(List<Review> rList);
serviceImpl
@Override
public List<ReviewAttachment> selectThumbnailList(List<Review> rList) {
return dao.selectThumbnailList(rList);
}
DAO
public List<ReviewAttachment> selectThumbnailList(List<Review> rList) {
return sqlSession.selectList("reviewMapper.selectThumbnailList",rList);
}
mapper
<select id="selectThumbnailList" parameterType="list" resultMap="attachment_rm">
SELECT * FROM BOARD_FILE
WHERE FILE_LEVEL = 1
AND PARENT_BOARD_NO IN
<foreach collection="list" item="review" open="(" close=")" separator=",">
#{review.boardNo}
</foreach>
</select>
- 좋아요 목록 조회
목록에 좋아요 표시를 위해 좋아요 여부를 조회해 온다.
service
List<ReviewLike> selectLike(int memberNo);
serviceImpl
@Override
public List<ReviewLike> selectLike(int memberNo) {
return dao.selectLike(memberNo);
}
DAO
public List<ReviewLike> selectLike(int memberNo) {
return sqlSession.selectList("reviewMapper.selectLike",memberNo);
}
mapper
<select id="selectLike" parameterType="_int" resultMap="like_rm">
SELECT
BOARD_NO, MEM_NO
FROM BOARD_LIKE
WHERE MEM_NO = #{memberNo}
</select>
- 전달 받은 데이터를 화면에 뿌려주기
CSS
<style>
.boardName {
margin-right: 40px;
}
.boardTitleBorder {
border-bottom: gray 1px solid;
}
.card-img-top {
height: 15rem;
}
.categoryArea, .arrayArea {
display: inline-block;
}
.category, .array {
text-decoration: none;
color: black;
line-height: 54px;
margin-right: 5px;
}
.category:click {
color: orange;
text-weight: bold;
}
.viewdetail:hover {
cursor: pointer;
}
/* 제목 */
.text-dark {
display: block;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size : 17px;
}
.infoAreaWrapper{
padding : 24px 24px 5px 24px;
}
/* 좋아요/댓글 */
.viewArea, .replyArea {
display: inline-block;
font-size: 11px;
margin-right: 5px;
}
.nickNameArea {
clear: both;
margin-bottom : 3px;
}
.icon {
width: 13px;
}
/* 검색창 */
.search {
text-align: center;
}
/******* 페이징 *******/
.flex {
-webkit-box-flex: 1;
-ms-flex: 1 1 auto;
flex: 1 1 auto
}
#page-content {
margin-top: 20px;
}
.pagination, .jsgrid .jsgrid-pager {
display: flex;
padding-left: 0;
list-style: none;
border-radius: 0.25rem
}
.page-item>a, .page-item>a:hover {
color: black;
}
.pagination.pagination-rounded-flat .page-item {
margin: 0 .1rem
}
.pagination-success .page-item.active .page-link {
background: #00c689;
border-color: #00c689
}
.pagination.pagination-rounded-flat .page-item .page-link {
border: none;
border-radius: 50px;
}
.pg {
flex: none !important;
max-width: none !important;
}
.like2 {
background-size : 11px;
background-image: url('${contextPath}/resources/images/like2.png');
background-repeat: no-repeat;
}
.maincolor-re1{
color: #4ab34a !important;
background-color: #ffffff !important;
border: none !important;
}
.maincolor-re1:hover{
color: #ffffff !important;
background-color:#4ab34a !important;
border: 1px solid #4ab34a !important;
}
</style>
JSP
- 주소 조합 작업
검색 결과가 있을 경우 카테고리, 정렬, 검색 옵션,검색어에 따라 searchStr변수에 저장한다.
그 후 pageUrl에 searchStr변수를 가지고 search로 매핑되게 해준다.
나중에 상세조회 후 목록으로 버튼을 눌렀을때 검색 내용이 유지되기 위해
목록으로 버튼에 사용할 URL저장 변수를 session scope에 올려준다.
<c:choose>
<c:when test="${!empty rSearch }">
<c:if test="${!empty rSearch.ct }">
<c:set var="category" value="ct=${rSearch.ct}&"/>
<c:set var="searchStr" value= "${category}"/>
</c:if>
<c:if test="${!empty rSearch.sort }">
<c:set var="sort" value="sort=${rSearch.sort}&"/>
<c:set var="searchStr" value="${category }${sort }"/>
</c:if>
<c:if test="${!empty rSearch.sv}">
<c:set var="sk" value="sk=${rSearch.sk}&"/>
<c:set var="sv" value="sv=${rSearch.sv}"/>
<c:set var="searchStr" value="${category}${sort}sk=${rSearch.sk}&sv=${rSearch.sv}&"/>
</c:if>
<c:url var="pageUrl" value="search?${searchStr}"/>
<!-- 목록으로 버튼에 사용할 URL저장 변수 session scope에 올리기-->
<c:set var="returnListURL" value="${contextPath}/review/${pageUrl}cp=${pInfo.currentPage}" scope="session" />
</c:when>
검색 결과가 없을 경우
pageUrl 은 ?로 선언해주고, 목록으로 버튼을 눌렀을때 일반 목록으로 돌아올 수 있도록 URL저장 변수를 session scope에 올려준다.
<c:otherwise>
<c:url var="pageUrl" value="?"/>
<!-- 목록으로 버튼에 사용할 URL저장 변수 session scope에 올리기-->
<c:set var="returnListURL" value="${contextPath}/review/list${pageUrl}cp=${pInfo.currentPage}" scope="session" />
</c:otherwise>
</c:choose>
- 목록 출력
카테고리와 정렬을 클릭했을 때 해당 페이지로 이동할 수 있도록 a 태그로 작성
EL${}에 값이 없으면 출력되지 않는다.
EX) search?ct=0&${sort}${sk}${sv}에서 ${sort}에는 정해진 값이 없다면
search?ct=0&${sk}${sv}로 동작한다.
<div class="container">
<!-- 게시판 이름/카테고리 -->
<div class="row py-5">
<div class="col-lg-12 mx-auto boardTitleBorder">
<div class="text-black banner">
<h1 class="boardName float-left">싱글이의 영수증</h1>
<div class="categoryArea">
<a class="category" id="0" href="search?ct=0&${sort}${sk}${sv}">
전체
</a>
<a class="category" id="1" href="search?ct=1&${sort}${sk}${sv}">
가구
</a>
<a class="category" id="2" href="search?ct=2&${sort}${sk}${sv}">
생활용품
</a>
<a class="category" id="3" href="search?ct=3&${sort}${sk}${sv}">
전자기기
</a>
<a class="category" id="4" href="search?ct=4&${sort}${sk}${sv}">
기타
</a>
</div>
<div class="arrayArea float-right">
<a class="array" id="newSort" href="search?${category}sort=new&${sk}${sv}">
최신순<img class="icon" src="${contextPath}/resources/images/arrow.png" />
</a>
<a class="array" id="likeSort" href="search?${category}sort=like&${sk}${sv}">
좋아요순<img class="icon" src="${contextPath}/resources/images/arrow.png" />
</a>
</div>
</div>
</div>
</div>
<!-- End -->
게시글의 날짜는 javaBean을 이용해 util.date클래스에서 현재 날짜를 가져와
오늘과 지난날의 형식을 다르게 출력하도록 구현
반복문을 실행해 좋아요 목록과 같은 게시글 번호라면 빨간 하트로 바뀌게 구현
해당하는 카테고리별로 색상을 다르게 지정
<div class="row">
<c:if test="${empty rList }">
<div class="col-lg-12">
<h5 style="text-aline: center;">존재하는 게시글이 없습니다.</h5>
</div>
</c:if>
<c:if test="${!empty rList }">
<c:forEach var="review" items="${rList}" varStatus="vs">
<!-- Gallery item -->
<div class="col-xl-4 col-lg-4 col-md-6 mb-4">
<div class="bg-white rounded shadow-sm viewdetail">
<!-- 썸네일 영역 -->
<div class="embed-responsive embed-responsive-4by3">
<c:set var="flag" value="true" />
<c:forEach var="thumbnail" items="${thList}">
<c:if test="${review.boardNo == thumbnail.parentBoardNo }">
<img src="${contextPath}${thumbnail.filePath}/${thumbnail.fileName}" class="img-fluid card-img-top embed-responsive-item">
<c:set var="flag" value="false" />
</c:if>
</c:forEach>
<c:if test="${flag=='true'}">
<img src="${contextPath}/resources/images/ReviewNonImages.png" class="img-fluid card-img-top embed-responsive-item">
</c:if>
</div>
<div class="infoAreaWrapper">
<h5>
<a class="text-dark">${review.boardTitle }</a>
</h5>
<div class="infoArea ">
<div class="viewArea float-left">
<jsp:useBean id="now" class="java.util.Date" />
<fmt:formatDate var="createDate" value="${review.createDate }" pattern="yyyy-MM-dd" />
<fmt:formatDate var="today" value="${now }" pattern="yyyy-MM-dd" />
<c:choose>
<c:when test="${createDate != today }">
${createDate }
</c:when>
<c:otherwise>
<fmt:formatDate value="${review.createDate}" pattern="HH:mm" />
</c:otherwise>
</c:choose>
</div>
<div class="viewArea mb-2 float-right">
<img class="icon" src="${contextPath}/resources/images/view.png" /> ${review.readCount }
<!-- 좋아요 -->
<span>
<img src="${contextPath}/resources/images/like1.png"
width="11" height="11" id="heart" class='likeImgs
<c:forEach var="like" items="${likeInfo}">
<c:if test="${like.boardNo == review.boardNo}">like2</c:if>
</c:forEach>'>
<span class="likeCnt" style="font-size:12px;">${review.likeCount}</span>
</button>
</span>
</div>
</div>
<div class="nickNameArea d-flex align-items-center justify-content-between rounded-pill bg-light px-3 py-2 " style="clear:both;">
<p class="small mb-0">
<span class="font-weight-bold price">${review.nickName }</span>
</p>
<div class='badge badge-danger px-3 rounded-pill font-weight-normal' style='
<c:if test="${review.categoryCode == '21'}">background-color: burlywood;</c:if>
<c:if test="${review.categoryCode == '22'}">background-color: #8dbe88;</c:if>
<c:if test="${review.categoryCode == '23'}">background-color: #5d8eb6d5;</c:if>
<c:if test="${review.categoryCode == '24'}">background-color: #d48a9a;</c:if>
'>${review.categoryName }</div>
</div>
</div>
<span id="boardNo" style="visibility: hidden">${review.boardNo }</span>
</div>
</div>
</c:forEach>
</c:if>
<!-- End -->
</div>
- 등록하기 버튼
로그인이 되어있고, 회원 2등급 이상일 경우 !=T
<c:if test="${!empty loginMember && loginMember.memberGrade != 'T' }">
<div class="row">
<div class="col-lg-12 mx-auto">
<button type="button" class="btn btn-success float-right maincolor-re" id="insertBoard">등록하기</button>
</div>
</div>
</c:if>
- 등록하기 버튼 script
$("#insertBoard").on("click", function() {
location.href = "insert";
});
- 페이징
<div class="page-content page-container" id="page-content">
<div class="padding">
<div class="row container d-flex justify-content-center">
<div class="col-md-4 col-sm-6 grid-margin stretch-card pg">
<nav>
<ul class="pagination d-flex justify-content-center flex-wrap pagination-rounded-flat pagination-success" style="margin-left:40px">
<c:set var="firstPage" value="${pageUrl}cp=1" />
<c:set var="lastPage" value="${pageUrl}cp=${pInfo.maxPage }" />
<fmt:parseNumber var="c1" value="${(pInfo.currentPage - 1) / 10 }" integerOnly="true" />
<fmt:parseNumber var="prev" value="${ c1 * 10 }" integerOnly="true" />
<c:set var="prevPage" value="${pageUrl}cp=${prev}" />
<fmt:parseNumber var="c2" value="${(pInfo.currentPage + 9) / 10 }" integerOnly="true" />
<fmt:parseNumber var="next" value="${ c2 * 10 + 1 }" integerOnly="true" />
<c:set var="nextPage" value="${pageUrl}cp=${next}" />
<c:if test="${pInfo.currentPage > pInfo.pageSize}">
<li class="page-item"><a class="page-link maincolor-re1" href="${firstPage }" data-abc="true"><i class="fas fa-angle-double-left"></i></a></li>
<li class="page-item"><a class="page-link maincolor-re1" href="${prevPage }" data-abc="true"><i class="fa fa-angle-left"></i></a></li>
</c:if>
<c:forEach var="page" begin="${pInfo.startPage }" end="${pInfo.endPage }">
<c:choose>
<c:when test="${pInfo.currentPage == page }">
<li class="page-item active">
<a class="page-link">${page}</a> <!-- 같은 페이지일때는 클릭이 안 된다. -->
</li>
</c:when>
<c:otherwise>
<li class="page-item">
<a class="page-link maincolor-re1" href="${pageUrl}cp=${page}">${page}</a>
</li>
</c:otherwise>
</c:choose>
</c:forEach>
<c:if test="${next <=pInfo.maxPage }">
<li class="page-item"><a class="page-link maincolor-re1" href="${nextPage }" data-abc="true"><i class="fa fa-angle-right"></i></a></li>
<li class="page-item"><a class="page-link maincolor-re1" href="${lastPage }" data-abc="true"><i class="fas fa-angle-double-right"></i></a></li>
</c:if>
</ul>
</nav>
</div>
</div>
</div>
</div>
- 검색창
검색어 입력시에도 카테고리와 정렬을 유지하기 위에 검색 폼 태크 내부에 input hidden태그로 카테고리와 정렬의 값을 세팅해준다.
<form action="search" class="row" id="searchForm" style="margin-bottom: 50px;">
<div class="col-md-12">
<div class="search">
<select name="sk" id="searchOption" style="width: 100px; height: 36px; display: inline-block;">
<option value="title">제목</option>
<option value="writer">작성자</option>
<option value="titcont">제목+내용</option>
</select> <input type="text" name="sv" class="form-control " autocomplete="off" style="width: 25%; display: inline-block;">
<button class="form-control btn btn-success maincolor " id="searchBtn" type="submit" style="width: 100px; display: inline-block; margin-bottom: 5px;">검색</button>
</div>
</div>
<input type="hidden" name="ct" value="${param.ct }"> <!-- 있으면 값 세팅 -->
<input type="hidden" name="sort" value="${param.sort }">
</form>
- 검색 파라미터 유지 script
$(function(){
// 카테고리
if(${param.ct == '0'}){
$("#0").css({"color":"#ffc823", "font-weight":"bold"});
}else if(${param.ct == '1'}){
$("#1").css({"color":"#ffc823", "font-weight":"bold"});
}else if(${param.ct == '2'}){
$("#2").css({"color":"#ffc823", "font-weight":"bold"});
}else if(${param.ct == '3'}){
$("#3").css({"color":"#ffc823", "font-weight":"bold"});
}else if(${param.ct == '4'}){
$("#4").css({"color":"#ffc823", "font-weight":"bold"});
}else{ // 선택 안된 경우,,
$("#0").css({"color":"#ffc823", "font-weight":"bold"});
}
// 정렬
if(${param.sort == 'like'}){
$("#likeSort").css({"color":"#ffc823", "font-weight":"bold"});
}else {
$("#newSort").css({"color":"#ffc823", "font-weight":"bold"});
}
// 검색 조건
$("select[name=sk] > option").each(function(index,item){
if($(item).val() == "${rSearch.sk}"){
$(this).prop("selected", true);
}
});
// 검색 내용
$("input[name=sv]").val("${rSearch.sv}");
});
공유하기
Twitter Google+ LinkedIn
댓글남기기