[싱글이의 영수증] 목록 조회

업데이트:

싱글이의 영수증 목록 조회

한 페이지에서 9개의 게시글을 볼 수 있으며, 그 이상의 게시글은 페이징바를 이용해서 페이지를 이동해서 볼 수 있게 구현했다.

  • 출력화면

image



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 } &nbsp;
                                
                        <!-- 좋아요  -->
                                 <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}");
    
});



댓글남기기