2022. 4. 26. 14:10ㆍ[Spring]_/[Spring]_포트폴리오 페이지 만들기
[환경]
개발툴 : IntelliJ
DB : oracle
프레임워크 : spring , mybatis
사용 언어 : ES6, Java , Html5 , CSS
완성 화면]
개발 목표 : 기본 게시판 그릴 때 로직 변경 -> 작성자 or 제목 항목 포함해서 query 발송,
ROWNUM 활용하여, bno(table 의 key) 를 숨기고 가상의 number 목록 표시
아래의 포스팅에서 로직 변경함
https://yn971106.tistory.com/77
[ 메뉴 1 의 전체 소스코드 ]
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<style>
.board {
width: 100%;
}
</style>
</head>
<body>
<div id="content">
<div id="contentwrap">
<h2>CRUD</h2>
<button class="regi">등록</button>
<select id="selectview">
<option value="10" selected="selected">10개씩 보기</option>
<option value="15">15개씩 보기</option>
<option value="20">20개씩 보기</option>
</select>
<select class="searchoption">
<option value="titlename">제목</option>
<option value="wirtername">작성자</option>
</select>
<input type="text" class="searchtext" value =""/>
<button class="detailsearch">조회</button>
<table border="1" class="board">
<thead>
<tr>
<th>번호</th>
<th>제목</th>
<th>글쓴이</th>
<th>작성일자</th>
<th>조회수</th>
</tr>
</thead>
<tbody id="table">
</tbody>
<!-- forEach 문은 리스트 객체 타입을 꺼낼때 많이 활용된다. -->
</table>
<span id="nav"></span>
</div>
</div>
</body>
</html>
<script>
$(document).ready(async function () {
let regibtn = document.querySelector('.regi');
let tbody = document.querySelector('#table');
let viewcnt = document.querySelector('#selectview');
let nav = document.querySelector('#nav');
let $common = $commons.history
let searchoption = document.querySelector('.searchoption');
let searchtext = document.querySelector('.searchtext');
let detailsearchbtn = document.querySelector('.detailsearch');
searchtext.onkeyup = function(e){
if(e.key === "Enter" || e.keyCode === 13 ){
detailsearchbtn.click();
}
}
viewcnt.onchange = function(){
detailsearchbtn.click();
}
regibtn.onclick = function () {
let url = "/regi/loader.do"
let parentContent = document.querySelector('#contentwrap');
let id = _commons().util.random();
$common.sethistory(url,parentContent,id);
let test = $common.gethistory(url);
console.log(test);
$('#content').load(url,function(){
});
}
detailsearchbtn.onclick = function(){
let wr;
let ti;
if(searchoption.value == "titlename" ){
ti = searchtext.value;
wr = "";
}else{
wr = searchtext.value;
ti = "";
}
createBoard(1,wr,ti);
createPagenation(wr,ti);
}
detailsearchbtn.click();
async function createPagenation(wr,ti) {
let cnt;
while(nav.firstChild){
nav.removeChild(nav.firstChild);
}
await fetch('/board/boardcount.do', {
method: "POST",
headers: {
'content-type': 'application/json'
},
body: JSON.stringify({
writer : wr,
title : ti
})
}).then(res => res.json())
.then(data => {
cnt = data;
})
//총 페이지 수
let totalpagecnt = Math.ceil(cnt / viewcnt.value);
//페이지 숫자 생성.
for (let i = 1; i <= totalpagecnt; i++) {
//html 생성하기.
let node = document.createElement('a');
const template = `
<a class="navclick" href="javascript:void(0)">\${i}</a>
`
node.innerHTML = template;
node.querySelector('a').onclick = function () {
createBoard(i,wr,ti);
}
nav.appendChild(node);
}
}
//테이블 생성
async function createBoard(nowpage,wr,ti) {
while(tbody.firstChild){
tbody.removeChild(tbody.firstChild);
}
await fetch('/board/search.do', {
method: "POST",
headers: {
'content-type': 'application/json'
},
body: JSON.stringify({
view: viewcnt.value, // ~만큼 보기
pagenum: nowpage, // 페이지 숫자클릭넘버.
writer : wr,
title : ti
})
}).then(res => res.json())
.then(data => {
for (let i = 0; i < data.length; i++) {
let node = document.createElement('tr');
const template = `
<th><a href="javascript:void(0)" class="boardlink">\${data[i].NUM}</a></th>
<th>\${data[i].title}</th>
<th>\${data[i].writer}</th>
<th>\${data[i].regdate}</th>
<th>\${data[i].viewcnt}</th>
`
node.innerHTML = template;
tbody.appendChild(node);
let boardlink = document.getElementsByClassName('boardlink');
boardlink[i].onclick =function(){
selectBoard(data[i].bno);
}
}
});
}
});
</script>
1] 조회 버튼 클릭 이벤트
detailsearchbtn.onclick = function(){
let wr;
let ti;
if(searchoption.value == "titlename" ){
ti = searchtext.value;
wr = "";
}else{
wr = searchtext.value;
ti = "";
}
createBoard(1,wr,ti);
createPagenation(wr,ti);
}
이 부분은 조회버튼을 클릭했을 때의 이벤트이다.
선택 박스에서 제목으로 검색할지, 작성자로 검색할지를 확인하고 그 값을 createBoard 함수, createPagenation에 각각 전달한다.
또한 이 함수는 곧 테이블을 만들고 페이징 처리도 해주는 함수이기 때문에 아래의 코드처럼 작성하고
searchtext.onkeyup = function(e){
if(e.key === "Enter" || e.keyCode === 13 ){
detailsearchbtn.click();
}
}
viewcnt.onchange = function(){
detailsearchbtn.click();
}
detailsearchbtn.click();
이 함수를 호출 하는 것으로 초기값, 검색, 갱신의 기능시 조회버튼을 click() 하는 것으로 대체하였습니다.
2] 테이블 생성 함수
async function createBoard(nowpage,wr,ti) {
while(tbody.firstChild){
tbody.removeChild(tbody.firstChild);
}
await fetch('/board/search.do', {
method: "POST",
headers: {
'content-type': 'application/json'
},
body: JSON.stringify({
view: viewcnt.value, // ~만큼 보기
pagenum: nowpage, // 페이지 숫자클릭넘버.
writer : wr,
title : ti
})
}).then(res => res.json())
.then(data => {
for (let i = 0; i < data.length; i++) {
let node = document.createElement('tr');
const template = `
<th><a href="javascript:void(0)" class="boardlink">\${data[i].NUM}</a></th>
<th>\${data[i].title}</th>
<th>\${data[i].writer}</th>
<th>\${data[i].regdate}</th>
<th>\${data[i].viewcnt}</th>
`
node.innerHTML = template;
tbody.appendChild(node);
let boardlink = document.getElementsByClassName('boardlink');
boardlink[i].onclick =function(){
selectBoard(data[i].bno);
}
}
});
}
2-1] 상세 설명
우선 생성시에는 이미 있는 테이블을 먼저 지우고 시작합니다.
tbody > 즉 column 명을 제외한 아래 값들을 removeChild 를 이용해서 지웁니다
그리고 fetch 를 통해 통신을 하게 되고
지정한 url 매핑으로 json 방식 통신을 진행합니다.
전달 값은
view -> 얼만큼 보려고 하는지
pagnum -> 현제 선택한 페이지 넘버
writer -> 작성자
title -> 제목
을 전달합니다.
그리고 전달받은 값은 list 구조로 오게 설정하였으며, 이를 length를 이용해 전부 for문을 태워서 반복합니다.
let node = document.createElement('tr');
const template = `
<th><a href="javascript:void(0)" class="boardlink">\${data[i].NUM}</a></th>
<th>\${data[i].title}</th>
<th>\${data[i].writer}</th>
<th>\${data[i].regdate}</th>
<th>\${data[i].viewcnt}</th>
`
node.innerHTML = template;
tbody.appendChild(node);
노드를 만들고,
그 노드 안의 html 구조인 template 를 만들어서 appendchild 를 이용하여 적용시킵니다.
template 안에 전달받은 내용을 기입하려면 \${ 데이터 } 로 적용이 가능합니다.
let boardlink = document.getElementsByClassName('boardlink');
boardlink[i].onclick =function(){
selectBoard(data[i].bno);
}
한줄을 그리고나서 바로 a 태그에 이벤트를 부착합니다. 해당 이벤트는 selectBoard 함수에 선택된 bno 의 값을 전달합니다.
selectBoard 는 다음 시간에 할 조회 기능 구현시 설명합니다.
2-2] Controller
@RequestMapping(value = "/board/search.do")
@ResponseBody
public String boardSearchPOST(@RequestBody Map<String, String> map) throws Exception{
System.out.println(" board 조회시작= ");
Integer viewcnt = Integer.parseInt(map.get("view"));
Integer nowpage = Integer.parseInt(map.get("pagenum"));
String title = map.get("title");
System.out.println("title = " + title);
String writer = map.get("writer");
System.out.println("writer = " + writer);
Integer start = viewcnt * (nowpage-1) +1;
Integer last = start + viewcnt -1;
System.out.println("start + last = " + start + last);
List list = boardService.boardSearch(start,last,writer,title);
System.out.println("list = " + list);
System.out.println(" board 조회끝= ");
String json = new Gson().toJson(list);
System.out.println("json = " + json);
return json;
}
받아온 Json 데이터를 key를 이용해 분할합니다.
그리고 페이징 처리를 위해 다음과 같은 로직으로 계산합니다
디비가 보여줄 첫 번째 순서 = 몇개씩 볼건지 전달받은 값 * ( 지금 내가 선택한 페이지 번호 -1 ) + 1
ex) 만약 10개씩 보기, 2번째 페이지 번호 클릭시에는 첫번째 순서는 10*1+1 즉 11 번째부터 보여준다는 의미입니다.
디비가 어디까지 보여줄건지 번호 = 디비가 보여줄 첫 번째 순서 + 얼만큼 볼건지 전달받은 값 - 1
ex) 만약 10개씩 보기, 2번째 페이지 번호 클릭시에는 11+10-1 즉 20번째 값 까지 보여주겠다는 의미입니다.
이렇게 2가지의 Integer 과 작성자, 제목 의 String 값 4개를 전달합니다.
※service serviceImpl 생략
2-3] BoardMapper
List<BoardVO> boardSearch(Integer start, Integer last, String writer,String title);
4가지 파라미터를 전달하고 VO에 지정한 List 배열의 형태로 리턴합니다.
2-4] Mapper.xml
<select id="boardSearch" resultType="com.yoon.model.BoardVO">
select bno, title, writer, regdate, viewcnt, NUM
from (select ROWNUM AS NUM, e.*
from (select * from board where writer like '%'||#{writer}||'%' and title like '%'||#{title}||'%' order by REGDATE desc) e)
where NUM between #{start} and #{last}
</select>
쿼리가 가장 중요한데 , 우선 bno, title, writer , regdate ,viewcnt ,NUM 이라는 6개의 컬럼을 반환합니다.
받아올 곳은 Oracle 에서 제공하는 가상의 ROWNUM 을 활용하여 해당 번호를 NUM 이라고 생성합니다.
이를 다시 board 라는 테이블에서 중첩질의를 하는데,
like 를 사용해서 전달받은 데이터와 비슷한지 비교해서 비슷한 값을 가져옵니다.
그리고 그 값들은 NUM 이라는 가상의 번호가 쭉 붙게 되고
그 중에서 Between을 사용해서 시작과 끝 사이의 값을 반환합니다.
그리고 BoardMapper 에서 VO 객체의 값 리스트로 받아온다고 하였으니
가상의 NUM 을 게시판에 보여줄 것이기 때문에 VO에 추가를 해줘야 합니다.
2-5] BoardVO]
package com.yoon.model;
import java.util.Date;
public class BoardVO {
//Field
private int bno; //게시물번호
private String title; //게시물제목
private String content; //게시물내용
private String writer; //게시물작성자
private String regdate; //게시물작성일자
private int viewcnt; //게시물조회수
//게시물 인덱스
private int NUM;
public int getNUM() {
return NUM;
}
public void setNUM(int NUM) {
this.NUM = NUM;
}
public int getBno() {
return bno;
}
public void setBno(int bno) {
this.bno = bno;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
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 getRegdate() {
return regdate;
}
public void setRegdate(String regdate) {
this.regdate = regdate;
}
public int getViewcnt() {
return viewcnt;
}
public void setViewcnt(int viewcnt) {
this.viewcnt = viewcnt;
}
@Override
public String toString() {
return "BoardVO{" +
"bno=" + bno +
", title='" + title + '\'' +
", content='" + content + '\'' +
", writer='" + writer + '\'' +
", regdate='" + regdate + '\'' +
", viewcnt=" + viewcnt +
", NUM=" + NUM +
'}';
}
}
getter 와 setter , 그리고 toString 만 해주면 됩니다. (단축키 alt + insert)
검색시 검색한 값과 비슷한 유형의 데이터를 선택한 범위 내에서 전달받을수 있게 되었습니다.
이제는 페이징 처리를 하겠습니다.
3-1] CreatePagenation
async function createPagenation(wr,ti) {
let cnt;
while(nav.firstChild){
nav.removeChild(nav.firstChild);
}
await fetch('/board/boardcount.do', {
method: "POST",
headers: {
'content-type': 'application/json'
},
body: JSON.stringify({
writer : wr,
title : ti
})
}).then(res => res.json())
.then(data => {
cnt = data;
})
//총 페이지 수
let totalpagecnt = Math.ceil(cnt / viewcnt.value);
//페이지 숫자 생성.
for (let i = 1; i <= totalpagecnt; i++) {
//html 생성하기.
let node = document.createElement('a');
const template = `
<a class="navclick" href="javascript:void(0)">\${i}</a>
`
node.innerHTML = template;
node.querySelector('a').onclick = function () {
createBoard(i,wr,ti);
}
nav.appendChild(node);
}
}
board 에 그려주는 값 외에도 하단의 페이지 숫자를 얼만큼 할건지에 대한 값도 계산해야 합니다.
이를 하기 위해서는 검색되는 값들의 총량을 알아야 합니다 -> Count() 쿼리
검색의 총량이기 때문에 시작과 끝 지점을 전달할 필요가 없습니다.
제목과, 작성자의 value 값만 전달하여 쿼리를 돌렸을때의 총량을 전달받도록 하였고,
총 페이지 숫자를 표현해야하는 수 = 쿼리 조회시 나온 row의 총합 / 얼만큼 보여줄건지 전달받은 값
ex) 만약 test 라는 작성자의 글이 51개가 있고, 10개씩 보여줄 것이라면, 51/ 10 -> 5.1 입니다. 올림처리 하여 6이 되고
1 2 3 4 5 6 총 6개의 페이지가 필요합니다. 각각 10개씩 보여줄 것이고, 마지막 6에는 하나의 데이터만 있게 됩니다.
이 총 페이지 수 만큼 for 문을 돌며 a 태그에 이벤트를 겁니다.
각각의 숫자는 a 태그이며 해당 태그를 클릭시 클릭한 페이지 수를 createBoard 함수에 전달함으로써 현제 클릭한 페이지 번호를 전달 할 수 있게 됩니다.
3-2] Controller
@RequestMapping("/board/boardcount.do")
@ResponseBody
public Integer searchcount(@RequestBody Map<String, String> map) throws Exception{
System.out.println("검색 총 수 파악");
String writer = map.get("writer");
System.out.println("writer = " + writer);
String title = map.get("title");
System.out.println("title = " + title);
Integer result = boardService.searchcnt(writer,title);
System.out.println("result = " + result);
return result;
}
마찬가지로 key 와 value를 구분하고 검색 조건만 전달합니다.
※service serviceImpl BoardMapper 생략
3-3] Mapper.xml
<select id="searchcnt" resultType="int">
select count(0)
from board
where writer like '%'||#{writer}||'%' and title like '%'||#{title}||'%'
</select>
일반 조회와 비슷하지만, ROWNUM을 할 필요가 없이 총량만 count 하여 Int 값으로 반환합니다.
이렇게 반환된 INT를 앞서 설명한 방식대로 동작하여 페이징 처리가 완료되었습니다.
감사합니다.
다음에는 중간에 잠시 나온 selectBoard 즉 row 선택시 상세정보가 나오게 하는 페이지를 구현해보겠습니다.
'[Spring]_ > [Spring]_포트폴리오 페이지 만들기' 카테고리의 다른 글
[포트폴리오 페이지]_16단계_CRUD 게시판 구현_(Update 기능) (0) | 2022.04.26 |
---|---|
[포트폴리오 페이지]_15단계_CRUD 게시판 구현_(목록 Read 기능) (0) | 2022.04.26 |
[포트폴리오 페이지]_13단계_CRUD 게시판 구현_(신규 데이터 Create) (0) | 2022.04.26 |
[포트폴리오 페이지]_12단계_SPA 구현을 위한 History 객체 만들기 (0) | 2022.04.26 |
[포트폴리오 페이지]_11단계_CRUD 게시판 구현_(feat.페이징 처리) (0) | 2022.04.18 |