일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 리소스모니터링
- Properties
- alter
- NIO
- deque
- date
- JPA
- Calendar
- Java
- string
- 힙덤프
- map
- 스택
- 스프링부트
- math
- scanner
- BFS
- GC로그수집
- union_find
- priority_queue
- set
- Union-find
- List
- CSS
- dfs
- html
- spring boot
- javascript
- 큐
- sql
- Today
- Total
매일 조금씩
Spring MVC 로 무한 스크롤, 검색 구현하기 본문
사진자랑 게시판(게시판명: picture)의 '무한스크롤', '검색' 기능을 예로 들었다.
검색한 경우에도 똑같이 무한 스크롤이 적용되어야 하므로 무한스크롤과 검색 기능을 같이 구현하였다.
*** 순서 ***
1. 구현 완료 모습
2. 코드
2-1. JSP
2-2. Controller
2-3. DAO
2-4. TO
2-5. SQL(mapper)
1. 구현 완료 모습
1) 무한 스크롤
2) 검색
검색 결과 갯수와 검색 결과가 출력된다.
검색 결과가 많은 경우, 무한스크롤로 구현된다.
2. 코드
2-1. JSP
> views > picture > picture_list.jsp
▶ 검색란, 무한스크롤로 가져온 리스트가 들어갈 부분 html
<div id="card-search" class="card-search" >
<!-- 검색 버튼과 form -->
<form action="./picture_list.do" method="get">
<div class="row justify-content-md-center" id="search">
<div class="col-1"></div>
<div class="col-1">
<div class="value" id="condition_pick" style="text-align:right;">
<select id="condition" name="condition" class="form-select">
<option value="subject" ${condition eq 'subject' ? 'selected' : '' }>제목</option>
<option value="content" ${condition eq 'content' ? 'selected' : '' }>내용</option>
<option value="writer" ${condition eq 'writer' ? 'selected' : '' }>작성자</option>
<option value="location" ${condition eq 'location' ? 'selected' : '' }>위치</option>
</select>
</div>
</div>
<div class="col-md-8" style="padding-left:0px; text-align:left;">
<input value="${keyword }" type="text" name="keyword" placeholder="검색어를 입력해주세요" class="form-control"
style="padding-left:0px;">
</div>
<div class="col-md-2" style="padding:0px; text-align:left;">
<span><button class="btn btn-success" type="submit">검색</button></span>
<span id="writebox">
<c:if test="${!empty sessionScope.nick}">
<c:choose>
<c:when test="${empty sessionScope.nick}">
<button type="button"
class="btn btn--radius-2 btn--blue-2"
onclick="javascript:alert('로그인을 하셔야합니다.')">글쓰기</button>
</c:when>
<c:otherwise>
<button type="button"
class="btn btn--radius-2 btn--blue-2"
onclick="location.href='./picture_write.do'">글쓰기</button>
</c:otherwise>
</c:choose>
</c:if>
</span>
</div>
</div>
</form>
</div>
<%-- 만일 검색 키워드가 존재한다면 몇개의 글이 검색 되었는지 알려준다. --%>
<c:if test="${not empty keyword }">
<div class="alert text-center">
<strong>${totalRow }</strong> 개의 자료가 검색되었습니다.
</div>
</c:if>
<!-- ======= card Section ======= -->
<section id="card-list" class="card-list">
<div class="container">
<div class="row card-list-container thumbnails"></div>
</div>
</section>
<!-- ======= card Section 끝 ======= -->
<div class="back-drop">
<!-- cpath/ 에서 '/'는 webapp을 의미한다. 웹앱 폴더의 svg폴더 안에 spinner-solid.svg가 들어있다. -->
<img src="./svg/spinner-solid.svg"/>
</div>
위에서 검색란 부분 코드이다.
<div id="card-search" class="card-search" >
<!-- 검색 버튼과 form -->
<form action="./picture_list.do" method="get">
<div class="row justify-content-md-center" id="search">
<div class="col-1"></div>
<div class="col-1">
<div class="value" id="condition_pick" style="text-align:right;">
<select id="condition" name="condition" class="form-select">
<option value="subject" ${condition eq 'subject' ? 'selected' : '' }>제목</option>
<option value="content" ${condition eq 'content' ? 'selected' : '' }>내용</option>
<option value="writer" ${condition eq 'writer' ? 'selected' : '' }>작성자</option>
<option value="location" ${condition eq 'location' ? 'selected' : '' }>위치</option>
</select>
</div>
</div>
<div class="col-md-8" style="padding-left:0px; text-align:left;">
<input value="${keyword }" type="text" name="keyword" placeholder="검색어를 입력해주세요" class="form-control"
style="padding-left:0px;">
</div>
<div class="col-md-2" style="padding:0px; text-align:left;">
<span><button class="btn btn-success" type="submit">검색</button></span>
<span id="writebox">
<c:if test="${!empty sessionScope.nick}">
<c:choose>
<c:when test="${empty sessionScope.nick}">
<button type="button"
class="btn btn--radius-2 btn--blue-2"
onclick="javascript:alert('로그인을 하셔야합니다.')">글쓰기</button>
</c:when>
<c:otherwise>
<button type="button"
class="btn btn--radius-2 btn--blue-2"
onclick="location.href='./picture_write.do'">글쓰기</button>
</c:otherwise>
</c:choose>
</c:if>
</span>
</div>
</div>
</form>
</div>
검색 결과 갯수 출력부분 코드이다.
<%-- 만일 검색 키워드가 존재한다면 몇개의 글이 검색 되었는지 알려준다. --%>
<c:if test="${not empty keyword }">
<div class="alert text-center">
<strong>${totalRow }</strong> 개의 자료가 검색되었습니다.
</div>
</c:if>
검색 전, 후 무한스크롤로 카드리스트를 가져와서 넣는 부분이다.
<!-- ======= card Section ======= -->
<section id="card-list" class="card-list">
<div class="container">
<div class="row card-list-container thumbnails"></div>
</div>
</section>
<!-- ======= card Section 끝 ======= -->
로딩 이미지 부분이다. 스크롤이 맨끝에 닿고 다음 페이지를 가져올때 동안 띄워진다.
<div class="back-drop">
<!-- cpath/ 에서 '/'는 webapp을 의미한다. 웹앱 폴더의 svg폴더 안에 spinner-solid.svg가 들어있다. -->
<img src="./svg/spinner-solid.svg"/>
</div>
▶ 스크롤시 이벤트 처리 javascript
//페이지가 처음 로딩될 때 1page를 보여주기 때문에 초기값을 1로 지정한다.
let currentPage=1;
//현재 페이지가 로딩중인지 여부를 저장할 변수이다.
let isLoading=false;
//웹브라우저의 창을 스크롤 할 때 마다 호출되는 함수 등록
$(window).on("scroll",function(){
//위로 스크롤된 길이
let scrollTop=$(window).scrollTop();
//웹브라우저의 창의 높이
let windowHeight=$(window).height();
//문서 전체의 높이
let documentHeight=$(document).height();
//바닥까지 스크롤 되었는 지 여부를 알아낸다.
let isBottom=scrollTop+windowHeight + 10 >= documentHeight;
if(isBottom){
//만일 현재 마지막 페이지라면
if(currentPage == ${totalPageCount} || isLoading){
return; //함수를 여기서 끝낸다.
}
//현재 로딩 중이라고 표시한다.
isLoading=true;
//로딩바를 띄우고
$(".back-drop").show();
//요청할 페이지 번호를 1 증가시킨다.
currentPage++;
//추가로 받아올 페이지를 서버에 ajax 요청을 하고
console.log("inscroll"+currentPage);
GetList(currentPage);
};
});
▶ 카드 리스트를 가져오는 함수 javascript
const GetList = function(currentPage){
console.log("inGetList"+currentPage);
// 무한 스크롤
$.ajax({
url:"ajax_page.do",
method:"GET",
//검색기능이 있는 경우 condition과 keyword를 함께 넘겨줘야한다. 안그러면 검색결과만 나와야하는데 다른것들이 덧붙여져 나온다.
data:"pageNum="+currentPage+"&condition=${condition}&keyword=${keyword}",
//ajax_page.jsp의 내용이 data로 들어온다.
success:function(data){
console.log(data);
//응답된 문자열은 html 형식이다.(picture/ajax_page.jsp에 응답내용이 있다.)
//해당 문자열을 .card-list-container div에 html로 해석하라고 추가한다.
$(".card-list-container").append(data);
//로딩바를 숨긴다.
$(".back-drop").hide();
//로딩중이 아니라고 표시한다.
isLoading=false;
console.log("ajax");
$(".heart-click").unbind('click');
// 로그인 한 상태에서 하트를 클릭했을 때 (로그인한 상태인 하트의 <a></a> class명: heart-click)
$(".heart-click").click(function() {
// 게시물 번호(no)를 idx로 전달받아 저장합니다.
let no = $(this).attr('idx');
console.log("heart-click");
// 빈하트를 눌렀을때
if($(this).children('svg').attr('class') == "bi bi-suit-heart"){
console.log("빈하트 클릭" + no);
$.ajax({
url : 'saveHeart.do',
type : 'get',
data : {
no : no,
},
success : function(pto) {
//페이지 새로고침
//document.location.reload(true);
let heart = pto.heart;
// 페이지, 모달창에 하트수 갱신
$('#m_heart'+no).text(heart);
$('#heart'+no).text(heart);
console.log("하트추가 성공");
},
error : function() {
alert('서버 에러');
}
});
console.log("꽉찬하트로 바껴라!");
// 꽉찬하트로 바꾸기
$(this).html("<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' class='bi bi-suit-heart-fill' viewBox='0 0 16 16'><path d='M4 1c2.21 0 4 1.755 4 3.92C8 2.755 9.79 1 12 1s4 1.755 4 3.92c0 3.263-3.234 4.414-7.608 9.608a.513.513 0 0 1-.784 0C3.234 9.334 0 8.183 0 4.92 0 2.755 1.79 1 4 1z'/></svg>");
$('.heart_icon'+no).html("<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' class='bi bi-suit-heart-fill' viewBox='0 0 16 16'><path d='M4 1c2.21 0 4 1.755 4 3.92C8 2.755 9.79 1 12 1s4 1.755 4 3.92c0 3.263-3.234 4.414-7.608 9.608a.513.513 0 0 1-.784 0C3.234 9.334 0 8.183 0 4.92 0 2.755 1.79 1 4 1z'/></svg>");
// 꽉찬 하트를 눌렀을 때
}else if($(this).children('svg').attr('class') == "bi bi-suit-heart-fill"){
console.log("꽉찬하트 클릭" + no);
$.ajax({
url : 'removeHeart.do',
type : 'get',
data : {
no : no,
},
success : function(pto) {
//페이지 새로고침
//document.location.reload(true);
let heart = pto.heart;
// 페이지, 모달창에 하트수 갱신
$('#m_heart'+no).text(heart);
$('#heart'+no).text(heart);
console.log("하트삭제 성공");
},
error : function() {
alert('서버 에러');
}
});
console.log("빈하트로 바껴라!");
// 빈하트로 바꾸기
$(this).html('<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-suit-heart" viewBox="0 0 16 16"><path d="M8 6.236l-.894-1.789c-.222-.443-.607-1.08-1.152-1.595C5.418 2.345 4.776 2 4 2 2.324 2 1 3.326 1 4.92c0 1.211.554 2.066 1.868 3.37.337.334.721.695 1.146 1.093C5.122 10.423 6.5 11.717 8 13.447c1.5-1.73 2.878-3.024 3.986-4.064.425-.398.81-.76 1.146-1.093C14.446 6.986 15 6.131 15 4.92 15 3.326 13.676 2 12 2c-.777 0-1.418.345-1.954.852-.545.515-.93 1.152-1.152 1.595L8 6.236zm.392 8.292a.513.513 0 0 1-.784 0c-1.601-1.902-3.05-3.262-4.243-4.381C1.3 8.208 0 6.989 0 4.92 0 2.755 1.79 1 4 1c1.6 0 2.719 1.05 3.404 2.008.26.365.458.716.596.992a7.55 7.55 0 0 1 .596-.992C9.281 2.049 10.4 1 12 1c2.21 0 4 1.755 4 3.92 0 2.069-1.3 3.288-3.365 5.227-1.193 1.12-2.642 2.48-4.243 4.38z" /></svg>');
$('.heart_icon'+no).html('<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-suit-heart" viewBox="0 0 16 16"><path d="M8 6.236l-.894-1.789c-.222-.443-.607-1.08-1.152-1.595C5.418 2.345 4.776 2 4 2 2.324 2 1 3.326 1 4.92c0 1.211.554 2.066 1.868 3.37.337.334.721.695 1.146 1.093C5.122 10.423 6.5 11.717 8 13.447c1.5-1.73 2.878-3.024 3.986-4.064.425-.398.81-.76 1.146-1.093C14.446 6.986 15 6.131 15 4.92 15 3.326 13.676 2 12 2c-.777 0-1.418.345-1.954.852-.545.515-.93 1.152-1.152 1.595L8 6.236zm.392 8.292a.513.513 0 0 1-.784 0c-1.601-1.902-3.05-3.262-4.243-4.381C1.3 8.208 0 6.989 0 4.92 0 2.755 1.79 1 4 1c1.6 0 2.719 1.05 3.404 2.008.26.365.458.716.596.992a7.55 7.55 0 0 1 .596-.992C9.281 2.049 10.4 1 12 1c2.21 0 4 1.755 4 3.92 0 2.069-1.3 3.288-3.365 5.227-1.193 1.12-2.642 2.48-4.243 4.38z" /></svg>');
}
});
// 로그인 안한 상태에서 하트를 클릭하면 로그인해야한다는 알림창이 뜹니다.
// (로그인한 상태인 하트의 <a></a> class명: heart-notlogin)
$(".heart-notlogin").unbind('click');
$(".heart-notlogin ").click(function() {
alert('로그인 하셔야 하트를 누를수 있습니다!');
});
// 댓글아이콘을 클릭했을때 댓글 리스트 함수를 호출
$(".open_reply_list").unbind('click');
$(".open_reply_list").click(function(){
let no = $(this).attr('idx');
// 게시물에 no에 해당하는 댓글 리스트를 가져오는 함수
ReplyList(no);
});
// 댓글 달기 버튼 클릭시 실행
$(".write_reply").unbind('click');
$(".write_reply").click(function(){
// 게시물 번호
let no = $(this).attr('idx');
//책갈피
// 댓글 입력란의 내용을 가져온다.
let content = $("#input_reply" + no).val();
// 앞뒤 공백을 제거한다.(띄어쓰기만 입력했을때 댓글작성안되게 처리하기위함)
content = content.trim();
console.log(content);
if(content == ""){ // 입력된게 없을때
alert("글을 입력하세요!");
}else{
// 입력란 비우기
$("#input_reply" + no).val("");
// reply+1 하고 그 값을 가져옴
$.ajax({
url : 'picture_write_reply.do',
type : 'get',
data : {
no : no,
content: content
},
success : function(pto) {
let reply = pto.reply;
// 페이지, 모달창에 댓글수 갱신
$('#m_reply'+no).text(reply);
$('#reply'+no).text(reply);
console.log("댓글 작성 성공");
// 댓글리스트를 새로 받아오기
ReplyList(no);
},
error : function() {
alert('서버 에러');
}
});
}
});
}
});
}
위의 함수(GetList)는 페이지가 맨첨에 열릴 때, 혹은 검색으로 인해 페이지가 리로드 될 때는 아래처럼 함수가 호출되고..
$(document).ready(function(){
GetList(1);
});
그 이후 스크롤이 발생할 땐 위의 ▶ 스크롤시 이벤트 처리 javascript 에서 아래처럼 호출된다.
2-2. Controller
> PictureController.java
// 사진자랑 목록
@RequestMapping(value = "/picture_list.do")
public String picture_list(HttpServletRequest request, HttpSession session) {
// 한 페이지에 몇개씩 표시할 것인지
final int PAGE_ROW_COUNT = 12;
// 보여줄 페이지의 번호를 일단 1이라고 초기값 지정
int pageNum = 1;
// 페이지 번호가 파라미터로 전달되는지 읽어와 본다.
String strPageNum = request.getParameter("pageNum");
// 만일 페이지 번호가 파라미터로 넘어 온다면
if (strPageNum != null) {
// 숫자로 바꿔서 보여줄 페이지 번호로 지정한다.
pageNum = Integer.parseInt(strPageNum);
}
// 보여줄 페이지의 시작 ROWNUM - 0부터 시작
int startRowNum = 0 + (pageNum - 1) * PAGE_ROW_COUNT;
// 보여줄 페이지의 끝 ROWNUM
int endRowNum = pageNum * PAGE_ROW_COUNT;
int rowCount = PAGE_ROW_COUNT;
/*
* 검색 키워드 관련된 처리 - 검색 키워드가 파라미터로 넘어올 수도 있고 안넘어올 수도 있다.
*/
String keyword = request.getParameter("keyword");
String condition = request.getParameter("condition");
// 만일 키워드가 넘어오지 않는다면
if (keyword == null) {
// 키워드와 검색 조건에 빈 문자열을 넣어준다.
keyword = "";
condition = "";
}
// 특수기호를 인코딩한 키워드를 미리 준비한다.
String encodedK = URLEncoder.encode(keyword);
// startRowNum 과 rowCount 를 PictureTO 객체에 담는다.
PictureTO pto = new PictureTO();
pto.setStartRowNum(startRowNum);
pto.setEndRowNum(endRowNum);
pto.setRowCount(rowCount);
// ArrayList 객체의 참조값을 담을 지역변수를 미리 만든다.
ArrayList<PictureTO> list = null;
// 전체 row의 개수를 담을 지역변수를 미리 만든다. - 검색조건이 들어올 경우 '검색 결과 갯수'가 된다.
int totalRow = 0;
// 만일 검색 키워드가 넘어온다면
if (!keyword.equals("")) { // 검색 조건이 무엇이냐에 따라 분기하기
if (condition.equals("subject")) { // 제목 검색인 경우
// 검색 키워드를 PictureTO에 담아서 전달한다.
pto.setSubject(keyword);
} else if (condition.equals("content")) { // 내용 검색인 경우
pto.setContent(keyword);
} else if (condition.equals("writer")) { // 작성자 검색인 경우
pto.setWriter(keyword);
} else if (condition.equals("location")) { // 위치 검색인 경우
pto.setLocation(keyword);
} // 다른검색 조건을 추가하고 싶다면 아래 else if()를 계속 추가하면 된다.
}
// 위의 분기에 따라 pto에 담기는 내용이 다르고
// 그 내용을 기준으로 조건이 다를때마다 다른 내용이 select 되도록 dynamic query를 구성한다.
// 글 목록 얻어오기
if (session.getAttribute("nick") == null) {
// 로그인 상태가 아닐때
// System.out.println("로그인 상태가 아닐때 ");
// 사진 자랑 게시판 목록 가져오기
list = pictureDao.boardList(pto);
} else {
// 로그인 상태일때
// System.out.println("로그인 상태일때 ");
// 현재사용자의 nick을 세팅
pto.setNick((String) session.getAttribute("nick"));
// 사진자랑 게시판 목록 가져오기
list = pictureDao.boardListLogin(pto);
}
// 글의 개수
totalRow = pictureDao.PictureCount(pto);
// 전체 페이지의 갯수 구하기
int totalPageCount = (int) Math.ceil(totalRow / (double) PAGE_ROW_COUNT);
request.setAttribute("list", list);
request.setAttribute("totalPageCount", totalPageCount);
request.setAttribute("condition", condition);
request.setAttribute("keyword", keyword);
request.setAttribute("totalRow", totalRow);
// pageNum 도 추가로 담아주기
request.setAttribute("pageNum", pageNum);
/////////////////////////////////////////////////////// 여기까지 picture_ajax_page와 동일
/////////////////////////////////////////////////////// ajax_page는 스크롤을 내릴때 추가되는 게시물들을 가져오기 떄문에
/////////////////////////////////////////////////////// best5를 가져갈 필요가 없다.
// 사진자랑 좋아요(heart)순으로 상위5개 가져오기
ArrayList<PictureTO> bestList = new ArrayList();
bestList = pictureDao.bestList(pto);
request.setAttribute("bestList", bestList);
return "picture/picture_list";
}
// 사진자랑 목록 - 로딩으로 불러오는 게시물 리스트
@RequestMapping(value = "/ajax_page.do")
public String picture_ajax_page(HttpServletRequest request, HttpSession session) {
// 한 페이지에 몇개씩 표시할 것인지
final int PAGE_ROW_COUNT = 12;
// 보여줄 페이지의 번호를 일단 1이라고 초기값 지정
int pageNum = 1;
// 페이지 번호가 파라미터로 전달되는지 읽어와 본다.
String strPageNum = request.getParameter("pageNum");
// 만일 페이지 번호가 파라미터로 넘어 온다면
if (strPageNum != null) {
// 숫자로 바꿔서 보여줄 페이지 번호로 지정한다.
pageNum = Integer.parseInt(strPageNum);
}
// 보여줄 페이지의 시작 ROWNUM - 0부터 시작
int startRowNum = 0 + (pageNum - 1) * PAGE_ROW_COUNT;
// 보여줄 페이지의 끝 ROWNUM
int endRowNum = pageNum * PAGE_ROW_COUNT;
int rowCount = PAGE_ROW_COUNT;
/*
* 검색 키워드 관련된 처리 -검색 키워드가 파라미터로 넘어올 수도 있고 안넘어올 수도 있다.
*/
String keyword = request.getParameter("keyword");
String condition = request.getParameter("condition");
// 만일 키워드가 넘어오지 않는다면
if (keyword == null) {
// 키워드와 검색 조건에 빈 문자열을 넣어준다.
keyword = "";
condition = "";
}
// 특수기호를 인코딩한 키워드를 미리 준비한다.
String encodedK = URLEncoder.encode(keyword);
// startRowNum 과 rowCount 를 PictureTO 객체에 담고
PictureTO pto = new PictureTO();
pto.setStartRowNum(startRowNum);
pto.setEndRowNum(endRowNum);
pto.setRowCount(rowCount);
// ArrayList 객체의 참조값을 담을 지역변수를 미리 만든다.
ArrayList<PictureTO> list = null;
// 전체 row의 개수를 담을 지역변수를 미리 만든다.
int totalRow = 0;
// 만일 검색 키워드가 넘어온다면
if (!keyword.equals("")) { // 검색 조건이 무엇이냐에 따라 분기하기
if (condition.equals("subject")) { // 제목 검색인 경우
// 검색 키워드를 PictureTO에 담아서 전달한다.
pto.setSubject(keyword);
} else if (condition.equals("content")) { // 내용 검색인 경우
pto.setContent(keyword);
} else if (condition.equals("writer")) { // 작성자 검색인 경우
pto.setWriter(keyword);
} else if (condition.equals("location")) { // 위치 검색인 경우
pto.setLocation(keyword);
} // 다른검색 조건을 추가하고 싶다면 아래 else if()를 계속 추가하면 된다.
}
// 위의 분기에 따라 pto에 담기는 내용이 다르고
// 그 내용을 기준으로 조건이 다를때마다 다른 내용이 select 되도록 dynamic query를 구성한다.
// 글 목록 얻어오기
if (session.getAttribute("nick") == null) {
// 로그인 상태가 아닐때
System.out.println("로그인 상태가 아닐때 ");
// 사진 자랑 게시판 목록 가져오기
list = pictureDao.boardList(pto);
} else {
// 로그인 상태일때
// System.out.println("로그인 상태일때 ");
// 현재사용자의 nick을 세팅
pto.setNick((String) session.getAttribute("nick"));
// 사진자랑 게시판 목록 가져오기
list = pictureDao.boardListLogin(pto);
}
// 글의 개수
totalRow = pictureDao.PictureCount(pto);
// 전체 페이지의 갯수 구하기
int totalPageCount = (int) Math.ceil(totalRow / (double) PAGE_ROW_COUNT);
request.setAttribute("list", list);
request.setAttribute("totalPageCount", totalPageCount);
request.setAttribute("condition", condition);
request.setAttribute("keyword", keyword);
request.setAttribute("totalRow", totalRow);
// pageNum 도 추가로 담아주기
request.setAttribute("pageNum", pageNum);
/////////////////////////////////////////////////////// 여기까지 picture_list와 동일
/////////////////////////////////////////////////////// ajax_page는 스크롤을 내릴때 추가되는 게시물들을 가져오기 떄문에
/////////////////////////////////////////////////////// best5를 가져갈 필요가 없다.
return "picture/ajax_page";
}
페이지가 열릴때와 스크롤일때 요청을 나눠놨다.
페이지가 열릴때는 검색후 리로드되는 것도 포함하며 스크롤일때와 달리 검색결과에 맞는 best5를 추가로 가져온다는 차이가 있다.
2-3. DAO
> PictureDAO.java
// 로그인 전 게시판 list
public ArrayList<PictureTO> boardList(PictureTO to) {
// 게시물 리스트 가져오기
ArrayList<PictureTO> list = (ArrayList) sqlSession.selectList("picture_list", to);
return list;
}
// 로그인 후 게시판 list
public ArrayList<PictureTO> boardListLogin(PictureTO to) {
//게시물 리스트 가져오기 - 현재사용자의 nick이 nick에 세팅된 to를 넘긴다.
ArrayList<PictureTO> list = (ArrayList) sqlSession.selectList("picture_list_login", to);
return list;
}
// 게시물 갯수 가져오기
public int PictureCount(PictureTO to) {
// 게시물 갯수를 구한다.
// 검색 키워드가 들어온 경우 검색결과의 게시물갯수가 된다.
int result = sqlSession.selectOne("picture_count", to);
return result;
}
// 베스트5 list
public ArrayList<PictureTO> bestList(PictureTO to) {
ArrayList<PictureTO> bestList = (ArrayList) sqlSession.selectList("best_picture_list", to);
//System.out.println(bestList);
return bestList;
}
2-4. TO
> PictureTO.java
package com.exam.model1.picture;
import java.util.ArrayList;
import com.exam.model1.pictureReply.ReplyTO;
public class PictureTO {
private String no;
private String subject;
private String content;
private String writer;
private String wdate;
private String hit;
private String location;
private String media;
private String reply;
private String heart;
// 현재사용자가 좋아요 누른건지 아닌지
private String hno;
// 현재사용자가 즐겨찾기 누른건지 아닌지
private String fno;
// 글쓴이 프로필 사진
private String profile;
// 현재 사용자 id
private String nick;
// 시작 게시물 번호
private int startRowNum;
// 끝 게시물 번호
private int endRowNum;
// 가져갈 게시물 갯수
private int rowCount;
public int getRowCount() {
return rowCount;
}
public void setRowCount(int rowCount) {
this.rowCount = rowCount;
}
public int getStartRowNum() {
return startRowNum;
}
public void setStartRowNum(int startRowNum) {
this.startRowNum = startRowNum;
}
public int getEndRowNum() {
return endRowNum;
}
public void setEndRowNum(int endRowNum) {
this.endRowNum = endRowNum;
}
public String getNick() {
return nick;
}
public void setNick(String nick) {
this.nick = nick;
}
public String getProfile() {
return profile;
}
public void setProfile(String profile) {
this.profile = profile;
}
public String getHno() {
return hno;
}
public void setHno(String hno) {
this.hno = hno;
}
public String getFno() {
return fno;
}
public void setFno(String fno) {
this.fno = fno;
}
public String getNo() {
return no;
}
public void setNo(String no) {
this.no = no;
}
public String getSubject() {
return subject;
}
public void setSubject(String subject) {
this.subject = subject;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getWriter() {
return writer;
}
public void setWriter(String writer) {
this.writer = writer;
}
public String getWdate() {
return wdate;
}
public void setWdate(String wdate) {
this.wdate = wdate;
}
public String getHit() {
return hit;
}
public void setHit(String hit) {
this.hit = hit;
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
public String getMedia() {
return media;
}
public void setMedia(String media) {
this.media = media;
}
public String getReply() {
return reply;
}
public void setReply(String reply) {
this.reply = reply;
}
public String getHeart() {
return heart;
}
public void setHeart(String heart) {
this.heart = heart;
}
}
2-5. SQL(mapper)
> mapper.xml
<!-- 게시물 갯수 가져오기 - 검색기능이 추가되었을 때 sql문이 바껴야한다. -->
<select id="picture_count" parameterType="com.exam.model1.picture.PictureTO" resultType="int">
select count(no)
from p_board
<choose>
<!-- if문과 비슷한 구조임, subject가 둘 null이 아니라면, 제목 검색 -->
<when test="subject != null">
where (subject like CONCAT('%',#{subject},'%'))
</when>
<!--content가 null이 아니라면-->
<when test="content != null">
where (content like CONCAT('%',#{content},'%'))
</when>
<!--writer가 null이 아니라면-->
<when test="writer != null">
where (writer like CONCAT('%',#{writer},'%'))
</when>
<!--location이 null이 아니라면-->
<when test="location != null">
where (location like CONCAT('%',#{location},'%'))
</when>
</choose>
</select>
<select id="picture_favorite_count" parameterType="com.exam.model1.pictureHeart.PictureHeartTO" resultType="int">
select count(*)
from p_heart
where userid like '${ userid }'
</select>
<!-- 로그인 전 사진자랑 리스트 -->
<select id="picture_list" resultType="com.exam.model1.picture.PictureTO">
select b.no, subject, content, writer, date_format(wdate,'%Y-%m-%d') wdate, hit, location, media, reply, heart, u.profile
from p_board b left outer join user u
on b.writer = u.nick
<choose>
<!-- if문과 비슷한 구조임, subject가 둘 null이 아니라면, 제목 검색 -->
<when test="subject != null">
where (subject like CONCAT('%',#{subject},'%'))
</when>
<!--content가 null이 아니라면-->
<when test="content != null">
where (content like CONCAT('%',#{content},'%'))
</when>
<!--writer가 null이 아니라면-->
<when test="writer != null">
where (writer like CONCAT('%',#{writer},'%'))
</when>
<!--location이 null이 아니라면-->
<when test="location != null">
where (location like CONCAT('%',#{location},'%'))
</when>
</choose>
order by b.no desc
limit #{startRowNum}, #{rowCount}
</select>
<!-- 로그인 후 사진자랑 리스트 -->
<select id="picture_list_login" parameterType="com.exam.model1.picture.PictureTO" resultType="com.exam.model1.picture.PictureTO">
select b.no, subject, content, writer, date_format(wdate,'%Y-%m-%d') wdate, hit, location, media, reply, heart, h.hno, f.fno, u.profile
from p_board b left outer join p_heart h
on #{nick} = h.userid and b.no = h.bno
left outer join p_favorite f
on #{nick} = f.userid and b.no = f.bno
left outer join user u
on b.writer = u.nick
<choose>
<!-- if문과 비슷한 구조임, subject가 둘 null이 아니라면, 제목 검색 -->
<when test="subject != null">
where (subject like CONCAT('%',#{subject},'%'))
</when>
<!--content가 null이 아니라면-->
<when test="content != null">
where (content like CONCAT('%',#{content},'%'))
</when>
<!--writer가 null이 아니라면-->
<when test="writer != null">
where (writer like CONCAT('%',#{writer},'%'))
</when>
<!--location이 null이 아니라면-->
<when test="location != null">
where (location like CONCAT('%',#{location},'%'))
</when>
</choose>
order by b.no desc
limit #{startRowNum}, #{rowCount}
</select>
<!-- 사진자랑 BEST5 리스트 -->
<select id="best_picture_list" parameterType="com.exam.model1.picture.PictureTO" resultType="com.exam.model1.picture.PictureTO">
select no, writer, hit, location, media, reply, heart
from p_board
<choose>
<!-- if문과 비슷한 구조임, subject가 둘 null이 아니라면, 제목 검색 -->
<when test="subject != null">
where (subject like CONCAT('%',#{subject},'%'))
</when>
<!--content가 null이 아니라면-->
<when test="content != null">
where (content like CONCAT('%',#{content},'%'))
</when>
<!--writer가 null이 아니라면-->
<when test="writer != null">
where (writer like CONCAT('%',#{writer},'%'))
</when>
<!--location이 null이 아니라면-->
<when test="location != null">
where (location like CONCAT('%',#{location},'%'))
</when>
</choose>
order by heart desc
limit 5
</select>
*** 참고 ***
> views > picture > ajax_page.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<c:forEach var="tmp" items="${list }">
<div class="col-3 card-inner">
<div class="thumb">
<div class="card-img" type="button" idx="${tmp.no }" data-bs-toggle="modal" data-bs-target="#viewModal${tmp.no }">
<div class="box">
<img src="./upload/picture/${tmp.media }" class="card-img-top img-wrapper"alt="...">
</div>
</div>
<div class="card-body">
<div class="row">
<div class="col-xsm card-title">${tmp.writer }</div>
<div class="col-sm card-heart" id="card-heart">
<c:choose>
<%-- 로그인 상태일때 - 하트 클릭 되게 --%>
<c:when test="${not empty sessionScope.nick}">
<c:choose>
<c:when test="${empty tmp.hno}">
<%-- 빈 하트일때 --%>
<span> <a idx="${tmp.no }" href="javascript:"
class="heart-click heart_icon${tmp.no }"><svg
xmlns="http://www.w3.org/2000/svg" width="16" height="16"
fill="currentColor" class="bi bi-suit-heart"
viewBox="0 0 16 16">
<path
d="M8 6.236l-.894-1.789c-.222-.443-.607-1.08-1.152-1.595C5.418 2.345 4.776 2 4 2 2.324 2 1 3.326 1 4.92c0 1.211.554 2.066 1.868 3.37.337.334.721.695 1.146 1.093C5.122 10.423 6.5 11.717 8 13.447c1.5-1.73 2.878-3.024 3.986-4.064.425-.398.81-.76 1.146-1.093C14.446 6.986 15 6.131 15 4.92 15 3.326 13.676 2 12 2c-.777 0-1.418.345-1.954.852-.545.515-.93 1.152-1.152 1.595L8 6.236zm.392 8.292a.513.513 0 0 1-.784 0c-1.601-1.902-3.05-3.262-4.243-4.381C1.3 8.208 0 6.989 0 4.92 0 2.755 1.79 1 4 1c1.6 0 2.719 1.05 3.404 2.008.26.365.458.716.596.992a7.55 7.55 0 0 1 .596-.992C9.281 2.049 10.4 1 12 1c2.21 0 4 1.755 4 3.92 0 2.069-1.3 3.288-3.365 5.227-1.193 1.12-2.642 2.48-4.243 4.38z" />
</svg></a>
</span>
</c:when>
<c:otherwise>
<%-- 꽉찬 하트일때 --%>
<span> <a idx="${tmp.no }" href="javascript:"
class="heart-click heart_icon${tmp.no }"><svg
xmlns="http://www.w3.org/2000/svg" width="16" height="16"
fill="currentColor" class="bi bi-suit-heart-fill"
viewBox="0 0 16 16">
<path
d="M4 1c2.21 0 4 1.755 4 3.92C8 2.755 9.79 1 12 1s4 1.755 4 3.92c0 3.263-3.234 4.414-7.608 9.608a.513.513 0 0 1-.784 0C3.234 9.334 0 8.183 0 4.92 0 2.755 1.79 1 4 1z" />
</svg></a>
</span>
</c:otherwise>
</c:choose>
</c:when>
<%-- 로그인 상태가 아닐때 - 하트클릭 안되게 --%>
<c:otherwise>
<span> <a href="javascript:" class="heart-notlogin">
<svg class="heart3" xmlns="http://www.w3.org/2000/svg"
width="16" height="16" fill="currentColor"
class="bi bi-suit-heart" viewBox="0 0 16 16">
<path
d="M8 6.236l-.894-1.789c-.222-.443-.607-1.08-1.152-1.595C5.418 2.345 4.776 2 4 2 2.324 2 1 3.326 1 4.92c0 1.211.554 2.066 1.868 3.37.337.334.721.695 1.146 1.093C5.122 10.423 6.5 11.717 8 13.447c1.5-1.73 2.878-3.024 3.986-4.064.425-.398.81-.76 1.146-1.093C14.446 6.986 15 6.131 15 4.92 15 3.326 13.676 2 12 2c-.777 0-1.418.345-1.954.852-.545.515-.93 1.152-1.152 1.595L8 6.236zm.392 8.292a.513.513 0 0 1-.784 0c-1.601-1.902-3.05-3.262-4.243-4.381C1.3 8.208 0 6.989 0 4.92 0 2.755 1.79 1 4 1c1.6 0 2.719 1.05 3.404 2.008.26.365.458.716.596.992a7.55 7.55 0 0 1 .596-.992C9.281 2.049 10.4 1 12 1c2.21 0 4 1.755 4 3.92 0 2.069-1.3 3.288-3.365 5.227-1.193 1.12-2.642 2.48-4.243 4.38z" />
</svg>
</a>
</span>
</c:otherwise>
</c:choose>
<span id="heart${tmp.no }">${tmp.heart }</span>
<%-- 댓글 아이콘 --%>
<span> <svg xmlns="http://www.w3.org/2000/svg" width="16"
height="16" fill="currentColor" class="bi bi-chat-dots"
viewBox="0 0 16 16">
<path
d="M5 8a1 1 0 1 1-2 0 1 1 0 0 1 2 0zm4 0a1 1 0 1 1-2 0 1 1 0 0 1 2 0zm3 1a1 1 0 1 0 0-2 1 1 0 0 0 0 2z" />
<path
d="M2.165 15.803l.02-.004c1.83-.363 2.948-.842 3.468-1.105A9.06 9.06 0 0 0 8 15c4.418 0 8-3.134 8-7s-3.582-7-8-7-8 3.134-8 7c0 1.76.743 3.37 1.97 4.6a10.437 10.437 0 0 1-.524 2.318l-.003.011a10.722 10.722 0 0 1-.244.637c-.079.186.074.394.273.362a21.673 21.673 0 0 0 .693-.125zm.8-3.108a1 1 0 0 0-.287-.801C1.618 10.83 1 9.468 1 8c0-3.192 3.004-6 7-6s7 2.808 7 6c0 3.193-3.004 6-7 6a8.06 8.06 0 0 1-2.088-.272 1 1 0 0 0-.711.074c-.387.196-1.24.57-2.634.893a10.97 10.97 0 0 0 .398-2z" />
</svg>
</span> <span id="reply${tmp.no }">${tmp.reply }</span>
<%-- 눈깔 아이콘 --%>
<span> <svg xmlns="http://www.w3.org/2000/svg" width="16"
height="16" fill="currentColor" class="bi bi-eye"
viewBox="0 0 16 16">
<path
d="M16 8s-3-5.5-8-5.5S0 8 0 8s3 5.5 8 5.5S16 8 16 8zM1.173 8a13.133 13.133 0 0 1 1.66-2.043C4.12 4.668 5.88 3.5 8 3.5c2.12 0 3.879 1.168 5.168 2.457A13.133 13.133 0 0 1 14.828 8c-.058.087-.122.183-.195.288-.335.48-.83 1.12-1.465 1.755C11.879 11.332 10.119 12.5 8 12.5c-2.12 0-3.879-1.168-5.168-2.457A13.134 13.134 0 0 1 1.172 8z" />
<path
d="M8 5.5a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5zM4.5 8a3.5 3.5 0 1 1 7 0 3.5 3.5 0 0 1-7 0z" />
</svg>
</span> <span id="hit${tmp.no }">${tmp.hit }</span>
</div>
</div>
</div>
</div>
</div>
<!-- view Modal -->
<div class="modal fade" id="viewModal${tmp.no }" tabindex="-1"
aria-labelledby="exampleModalLabel" aria-hidden="true" role="dialog">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="m_subject">[${tmp.location}] ${tmp.subject }</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"
aria-label="Close"></button>
</div>
<div class="modal-body">
<section class="modal-section">
<!-- 글쓴이 프로필 사진이 원형으로 나오는 부분 -->
<div class="row">
<div class="col-10">
<span id="m_writer_profile">
<a href="other_profile.do?other_nick=${tmp.writer }">
<img id="profileImage" src='./upload/profile/${tmp.profile }' />
</a>
</span> <span id="m_writer">${tmp.writer }</span>
</div>
<div class="col-2">${tmp.wdate }</div>
</div>
</section>
<section class="modal-section-media">
<div class="container" id="m_media">
<img class="w-100" id="media-image"
src='./upload/picture/${tmp.media }' />
</div>
</section>
<section class="modal-section">
<div id="m_heart_reply_hit">
<span id="m_heart_icon"> <c:choose>
<%-- 로그인 상태일때 - 하트 클릭 되게 --%>
<c:when test="${not empty sessionScope.nick}">
<c:choose>
<c:when test="${empty tmp.hno}">
<%-- 빈 하트일때 --%>
<a idx="${tmp.no}" href="javascript:"
class="heart-click heart_icon${tmp.no }"><svg
xmlns="http://www.w3.org/2000/svg" width="16" height="16"
fill="currentColor" class="bi bi-suit-heart"
viewBox="0 0 16 16">
<path
d="M8 6.236l-.894-1.789c-.222-.443-.607-1.08-1.152-1.595C5.418 2.345 4.776 2 4 2 2.324 2 1 3.326 1 4.92c0 1.211.554 2.066 1.868 3.37.337.334.721.695 1.146 1.093C5.122 10.423 6.5 11.717 8 13.447c1.5-1.73 2.878-3.024 3.986-4.064.425-.398.81-.76 1.146-1.093C14.446 6.986 15 6.131 15 4.92 15 3.326 13.676 2 12 2c-.777 0-1.418.345-1.954.852-.545.515-.93 1.152-1.152 1.595L8 6.236zm.392 8.292a.513.513 0 0 1-.784 0c-1.601-1.902-3.05-3.262-4.243-4.381C1.3 8.208 0 6.989 0 4.92 0 2.755 1.79 1 4 1c1.6 0 2.719 1.05 3.404 2.008.26.365.458.716.596.992a7.55 7.55 0 0 1 .596-.992C9.281 2.049 10.4 1 12 1c2.21 0 4 1.755 4 3.92 0 2.069-1.3 3.288-3.365 5.227-1.193 1.12-2.642 2.48-4.243 4.38z" />
</svg></a>
</c:when>
<c:otherwise>
<%-- 꽉찬 하트일때 --%>
<a idx="${tmp.no}" href="javascript:"
class="heart-click heart_icon${tmp.no }"><svg
xmlns="http://www.w3.org/2000/svg" width="16" height="16"
fill="currentColor" class="bi bi-suit-heart-fill"
viewBox="0 0 16 16">
<path
d="M4 1c2.21 0 4 1.755 4 3.92C8 2.755 9.79 1 12 1s4 1.755 4 3.92c0 3.263-3.234 4.414-7.608 9.608a.513.513 0 0 1-.784 0C3.234 9.334 0 8.183 0 4.92 0 2.755 1.79 1 4 1z" />
</svg></a>
</c:otherwise>
</c:choose>
</c:when>
<%-- 로그인 상태가 아닐때 - 하트클릭 안되게 --%>
<c:otherwise>
<a href="javascript:" class="heart-notlogin"> <svg
class="heart3" xmlns="http://www.w3.org/2000/svg" width="16"
height="16" fill="currentColor" class="bi bi-suit-heart"
viewBox="0 0 16 16">
<path
d="M8 6.236l-.894-1.789c-.222-.443-.607-1.08-1.152-1.595C5.418 2.345 4.776 2 4 2 2.324 2 1 3.326 1 4.92c0 1.211.554 2.066 1.868 3.37.337.334.721.695 1.146 1.093C5.122 10.423 6.5 11.717 8 13.447c1.5-1.73 2.878-3.024 3.986-4.064.425-.398.81-.76 1.146-1.093C14.446 6.986 15 6.131 15 4.92 15 3.326 13.676 2 12 2c-.777 0-1.418.345-1.954.852-.545.515-.93 1.152-1.152 1.595L8 6.236zm.392 8.292a.513.513 0 0 1-.784 0c-1.601-1.902-3.05-3.262-4.243-4.381C1.3 8.208 0 6.989 0 4.92 0 2.755 1.79 1 4 1c1.6 0 2.719 1.05 3.404 2.008.26.365.458.716.596.992a7.55 7.55 0 0 1 .596-.992C9.281 2.049 10.4 1 12 1c2.21 0 4 1.755 4 3.92 0 2.069-1.3 3.288-3.365 5.227-1.193 1.12-2.642 2.48-4.243 4.38z" />
</svg></a>
</c:otherwise>
</c:choose>
</span> <span id="m_heart${tmp.no }">${tmp.heart }</span>
<%-- 댓글 수 --%>
<span> <a idx="${tmp.no}" href="javascript:"
class="open_reply_list" data-bs-toggle="collapse"
data-bs-target="#reply_card${tmp.no }" aria-expanded="false"
aria-controls="collapseExample"> <svg
xmlns="http://www.w3.org/2000/svg" width="16" height="16"
fill="currentColor" class="bi bi-chat-dots"
viewBox="0 0 16 16">
<path
d="M5 8a1 1 0 1 1-2 0 1 1 0 0 1 2 0zm4 0a1 1 0 1 1-2 0 1 1 0 0 1 2 0zm3 1a1 1 0 1 0 0-2 1 1 0 0 0 0 2z" />
<path
d="M2.165 15.803l.02-.004c1.83-.363 2.948-.842 3.468-1.105A9.06 9.06 0 0 0 8 15c4.418 0 8-3.134 8-7s-3.582-7-8-7-8 3.134-8 7c0 1.76.743 3.37 1.97 4.6a10.437 10.437 0 0 1-.524 2.318l-.003.011a10.722 10.722 0 0 1-.244.637c-.079.186.074.394.273.362a21.673 21.673 0 0 0 .693-.125zm.8-3.108a1 1 0 0 0-.287-.801C1.618 10.83 1 9.468 1 8c0-3.192 3.004-6 7-6s7 2.808 7 6c0 3.193-3.004 6-7 6a8.06 8.06 0 0 1-2.088-.272 1 1 0 0 0-.711.074c-.387.196-1.24.57-2.634.893a10.97 10.97 0 0 0 .398-2z" />
</svg>
</a>
</span> <span id="m_reply${tmp.no }">${tmp.reply }</span>
<%-- 조회수 --%>
<span> <svg xmlns="http://www.w3.org/2000/svg" width="16"
height="16" fill="currentColor" class="bi bi-eye"
viewBox="0 0 16 16">
<path
d="M16 8s-3-5.5-8-5.5S0 8 0 8s3 5.5 8 5.5S16 8 16 8zM1.173 8a13.133 13.133 0 0 1 1.66-2.043C4.12 4.668 5.88 3.5 8 3.5c2.12 0 3.879 1.168 5.168 2.457A13.133 13.133 0 0 1 14.828 8c-.058.087-.122.183-.195.288-.335.48-.83 1.12-1.465 1.755C11.879 11.332 10.119 12.5 8 12.5c-2.12 0-3.879-1.168-5.168-2.457A13.134 13.134 0 0 1 1.172 8z" />
<path
d="M8 5.5a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5zM4.5 8a3.5 3.5 0 1 1 7 0 3.5 3.5 0 0 1-7 0z" />
</svg>
</span> <span id="m_hit${tmp.no }">${tmp.hit }</span>
</div>
</section>
<section class="modal-section">
<div id="m_content">${tmp.content }</div>
</section>
</div>
<div>
<!-- 댓글 -->
<div class="collapse" id="reply_card${tmp.no }">
<section class="modal-section">
<div class="card card-body">
<!-- 댓글 목록 -->
<div class="reply-list reply-list${tmp.no }">
<!-- 댓글이 목록이 들어가는 곳 -->
</div>
<!-- 댓글 작성 => 로그인한 상태여야만 댓글작성 칸이 나온다. -->
<c:if test="${not empty sessionScope.nick }">
<div class="row reply_write">
<div class="col-1">
<a href="other_profile.do?other_nick=${tmp.writer }">
<img id="write_reply_profileImage"
src="./upload/profile/${sessionScope.profile }" />
</a>
</div>
<div class="col-8" class="input_reply_div">
<input class="w-100 form-control" id="input_reply${tmp.no}"
type="text" placeholder="댓글입력...">
</div>
<div class="col-3 ">
<button type="button" idx="${tmp.no }"
class="btn btn-success mb-1 write_reply">댓글 달기</button>
</div>
</div>
</c:if>
</div>
</section>
</div>
</div>
<div id="modify_delete">
<%-- 수정/삭제버튼 --%>
<c:if test="${not empty nick and tmp.writer eq sessionScope.nick}">
<div class='modal-footer'>
<button type="button" class="btn btn-secondary"
data-bs-toggle="modal" data-bs-target="#modifyModal${tmp.no}">수정</button>
<button type="button" class="btn btn-dark" data-bs-toggle="modal"
data-bs-target="#deleteModal${tmp.no}">삭제</button>
</div>
</c:if>
</div>
</div>
</div>
</div>
<%-- view Modal 끝 --%>
<!-- modify Modal -->
<div class="modal fade" id="modifyModal${tmp.no}" tabindex="-1"
aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">modify confirm</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"
aria-label="Close"></button>
</div>
<div class="modal-body">수정페이지로 이동 하시겠습니까?</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary"
data-bs-dismiss="modal">아니요</button>
<button type="button" class="btn btn-primary"
onclick="location.href='./picture_modify.do?no=${tmp.no}'">네</button>
</div>
</div>
</div>
</div>
<!-- modify Modal 끝 -->
<!-- delete Modal -->
<div class="modal fade" id="deleteModal${tmp.no}" tabindex="-1"
aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLabel">delete confirm</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"
aria-label="Close"></button>
</div>
<div class="modal-body">정말 삭제하시겠습니까?!!</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary"
data-bs-dismiss="modal">아니요</button>
<button type="button" class="btn btn-primary"
onclick="location.href='javascript:PictureDelete(${tmp.no})'">네</button>
</div>
</div>
</div>
</div>
<!-- delete Modal 끝 -->
</c:forEach>
'빅데이터 플랫폼 구축을 위한 자바 개발자 양성과정 > 랜선여행 커뮤니티 프로젝트' 카테고리의 다른 글
Spring MVC 에 소셜로그인(카카오로그인) 구현하기 - JavaScript 키 (18) | 2021.04.27 |
---|---|
Spring MVC 로 메세지 기능 구현 (42) | 2021.04.27 |
Spring MVC 로 댓글(답글) 구현하기 (16) | 2021.04.21 |
Spring MVC 로 하트(좋아요) 구현하기 - 모달창 포함 (10) | 2021.04.21 |
Web socket을 활용한 실시간 댓글 알람 기능 구현 (0) | 2021.03.30 |