3년전인가 처음 게시판을 만들어보고 거의 처음인것 같다. 언제쯤이면 머릿속에 생각했던 내용들을 거침없이 만들 수 있는 날이 올까나. 암튼 게시판은 스스로 안보고 짤 수 있을 때까지 연습해보면 실력향상에 도움이 많이 되는 미니프로젝트긴하다. 나중에 게시판에 좀더 기능을 넣을 수도 있다. 예를 들면 db설계를 좀 해서 댓글 기능, 좋아요 기능, 조회기능 등을 넣어서 게시판을 확장 시킬 수 있다. 이번 포스팅에서는 아주 아주 심플한 게시판을 만들어서 페이징 기능의 핵심만 정리해본다.
네비게이션
아래는 파일 네비게이션이다. 우리가 사용할 파일들은 빨간점으로 표시해놓았다.

node js 기본셋팅(app.js)
app.js를 아래와 같이 작성한다. 게시판이랑 상관없는 로직도 있지만 지우지 않았다... var productRouter 부분 부터 자세히 보자. 우리가 만들 게시판과 직접 관련된 로직이다. 웹브라우저에 /main, /board 라고 적으면 ./routes/product.js에서 처리하게 해놓았다.그럼 products.js 페이지 부분으로 가보자
//모듈 임포트
var express = require('express');
var app = express()
var morgan = require('morgan') //로그 모듈 임포트
var mysql = require('mysql') //mysql 임포트
var bodyParser = require('body-parser');
//미들웨어 설정
app.use(morgan('short')) //로그 미들웨어
app.use(express.static('./public')) //기본 파일 폴더 위치 설정
app.use(bodyParser.urlencoded({extended:false}))
//라우트로 분리시켜주기
var userRouter = require('./routes/user.js')
//상품리스트 게시판~!
var productRouter = require('./routes/product.js')
app.use(userRouter)
app.use(productRouter)
//var PORT = process.env.PORT || 3003
//서버 가동
app.listen(3003,function(){
console.log("서버가동")
})
products.js (라우터)
가장 중요한 부분. 웹브라우저에서 넘어오는 url을 전부 이곳에서 받는다. 유심히 보아야 할 부분이 /main 부분과 /pasing/"cur 부분이다. 가장 처음으로 들어온 유저는 가장 첫페이지를 보여줘야 하기 때문에 기본값 1을 페이징 함수에 넘겼다. 나머지 설명은 주석에 적어 놓았다.
var express = require('express')
var router = express.Router()
var mysql = require('mysql')
var fs = require('fs')
var ejs = require('ejs')
var bodyParser = require('body-parser');
router.use(bodyParser.urlencoded({ extended: false }))
//게시판 페이징
router.get("/pasing/:cur", function (req, res) {
//페이지당 게시물 수 : 한 페이지 당 10개 게시물
var page_size = 10;
//페이지의 갯수 : 1 ~ 10개 페이지
var page_list_size = 10;
//limit 변수
var no = "";
//전체 게시물의 숫자
var totalPageCount = 0;
var queryString = 'select count(*) as cnt from products'
getConnection().query(queryString, function (error2, data) {
if (error2) {
console.log(error2 + "메인 화면 mysql 조회 실패");
return
}
//전체 게시물의 숫자
totalPageCount = data[0].cnt
//현제 페이지
var curPage = req.params.cur;
console.log("현재 페이지 : " + curPage, "전체 페이지 : " + totalPageCount);
//전체 페이지 갯수
if (totalPageCount < 0) {
totalPageCount = 0
}
var totalPage = Math.ceil(totalPageCount / page_size);// 전체 페이지수
var totalSet = Math.ceil(totalPage / page_list_size); //전체 세트수
var curSet = Math.ceil(curPage / page_list_size) // 현재 셋트 번호
var startPage = ((curSet - 1) * 10) + 1 //현재 세트내 출력될 시작 페이지
var endPage = (startPage + page_list_size) - 1; //현재 세트내 출력될 마지막 페이지
//현재페이지가 0 보다 작으면
if (curPage < 0) {
no = 0
} else {
//0보다 크면 limit 함수에 들어갈 첫번째 인자 값 구하기
no = (curPage - 1) * 10
}
console.log('[0] curPage : ' + curPage + ' | [1] page_list_size : ' + page_list_size + ' | [2] page_size : ' + page_size + ' | [3] totalPage : ' + totalPage + ' | [4] totalSet : ' + totalSet + ' | [5] curSet : ' + curSet + ' | [6] startPage : ' + startPage + ' | [7] endPage : ' + endPage)
var result2 = {
"curPage": curPage,
"page_list_size": page_list_size,
"page_size": page_size,
"totalPage": totalPage,
"totalSet": totalSet,
"curSet": curSet,
"startPage": startPage,
"endPage": endPage
};
fs.readFile('list.html', 'utf-8', function (error, data) {
if (error) {
console.log("ejs오류" + error);
return
}
console.log("몇번부터 몇번까지냐~~~~~~~" + no)
var queryString = 'select * from products order by id desc limit ?,?';
getConnection().query(queryString, [no, page_size], function (error, result) {
if (error) {
console.log("페이징 에러" + error);
return
}
res.send(ejs.render(data, {
data: result,
pasing: result2
}));
});
});
})
})
//메인화면
router.get("/main", function (req, res) {
console.log("메인화면")
//main 으로 들어오면 바로 페이징 처리
res.redirect('/pasing/' + 1)
});
//삭제
router.get("/delete/:id", function (req, res) {
console.log("삭제 진행")
getConnection().query('delete from products where id = ?', [req.params.id], function () {
res.redirect('/main')
});
})
//삽입 페이지
router.get("/insert", function (req, res) {
console.log("삽입 페이지 나와라")
fs.readFile('insert.html', 'utf-8', function (error, data) {
res.send(data)
})
})
//삽입 포스터 데이터
router.post("/insert", function (req, res) {
console.log("삽입 포스트 데이터 진행")
var body = req.body;
getConnection().query('insert into products(name,modelnumber,series) values (?,?,?)', [body.name, body.num, body.section], function () {
//응답
res.redirect('/main');
})
})
//수정 페이지
router.get("/edit/:id", function (req, res) {
console.log("수정 진행")
fs.readFile('edit.html', 'utf-8', function (error, data) {
getConnection().query('select * from products where id = ?', [req.params.id], function (error, result) {
res.send(ejs.render(data, {
data: result[0]
}))
})
});
})
//수정 포스터 데이터
router.post("/edit/:id", function (req, res) {
console.log("수정 포스트 진행")
var body = req.body;
getConnection().query('update products set name = ?, modelnumber = ?, series = ? where id = ?',
[body.name, body.num, body.section, req.params.id], function () {
res.redirect('/main')
})
})
//글상세보기
router.get("/detail/:id", function (req, res) {
console.log("수정 진행")
fs.readFile('detail.html', 'utf-8', function (error, data) {
getConnection().query('select * from products where id = ?', [req.params.id], function (error, result) {
res.send(ejs.render(data, {
data: result[0]
}))
})
});
})
//mysql db 연결 함수
var pool = mysql.createPool({
connectionLimit: 10,
host: 'localhost',
user: 'root',
database: 'wow',
password: '1111'
})
//디비 연결 함수
function getConnection() {
return pool
}
module.exports = router
list.html(조회)
products.js에서 데이터를 처리한 후 뿌려주는 화면이 list.html이다. products.js 에서 게시물 조회 하는 로직 아래
res.send(ejs.render(data, {
data: result,
pasing: result2
}));
이부분을 자세히 보자. mysql에서 가져온 데이터(result)와 페이징 관련 데이터 results2를 각각 data, pasing 키에 담았다! 이 키 값이 그대로 list.html로 넘어간다.
<!DOCTYPE html>
<html lang="ko">
<head>
<title>Bootstrap Example</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container">
<h2>상품테이블</h2>
<a href="/insert">추가하기</a>
<br>
<br>
<p>상품테이블 목록입니다.</p>
<table class="table table-striped">
<thead>
<tr>
<th>아이디</th>
<th>상품명</th>
<th>넘버</th>
<th>분류</th>
<th>수정</th>
<th>삭제</th>
</tr>
</thead>
<tbody>
<% data.forEach(function (item,index){ %>
<tr>
<td>
<%= item.id %>
</td>
<td>
<a href='/detail/<%= item.id %>'><%= item.name %></a>
</td>
<td>
<%= item.modelnumber %>
</td>
<td>
<%= item.series %>
</td>
<td>
<a href="/delete/<%= item.id %>">삭제</a>
</td>
<td>
<a href="/edit/<%= item.id %>">수정</a>
</td>
</tr>
<% })%>
</tbody>
</table>
</div>
<br>
<br>
<div class="container">
<ul class="pager">
<%
var curSet = pasing.curSet
var endPage = pasing.endPage
var startPage = pasing.startPage
var totalSet = pasing.totalSet
var totalPage = pasing.totalPage
var page_list_size = pasing.page_list_size
var page_size = pasing.page_size
var curPage = pasing.curPage
%>
<%
if(curSet > 1){
%>
<li value=(startPage -1) class="previous">
<a href='/pasing/<%= ( startPage - 1) %>'>이전페이지</a>
</li>
<%
}
%>
<%
for(var i = startPage; i <= endPage; i++){
if(i > totalPage){break;}
if(i == curPage){
%>
<li value=<%= i %> class='' >
<a href='/pasing/<%= i %>'>
<%= i %>
</a>
</li>
<%
}else{
%>
<li value=<%= i %> class='active' >
<a href='/pasing/<%= i %>'>
<%= i %>
</a>
</li>
<%
}
%>
<%
}
%>
<%
if(curSet < totalSet){
%>
<li value=( endPage + 1) class="next">
<a href='/pasing/<%= ( endPage + 1) %>'>다음페이지</a>
</li>
<%
}
%>
</ul>
</div>
<br>
<br>
</body>
</html>
결과확인


나머지 화면 페이지부분은 게시판 페이징 기능 구현과 관련이 거의 없어서(?) 추가로 설명을 생략하겠습니다.
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<title>Bootstrap Example</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container">
<div class="jumbotron">
<h1>메인화면</h1>
<p> </p>
</div>
<p>.</p>
<a href="/main">메인화면으로</a>
</div>
</body>
</html>
insert.html
<!DOCTYPE html>
<html lang="en">
<head>
<title>물품관리</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container">
<h2>상품입력</h2>
<p>.</p>
<form action="/insert" method="POST">
<div class="form-group">
<label for="name">상품이름:</label>
<input type="text" class="form-control" id="name" name= "name">
</div>
<div class="form-group">
<label for="num">번호</label>
<input type="text" class="form-control" id="num" name="num">
</div>
<div class="form-group">
<label for="section">분류</label>
<input type="text" class="form-control" id="section" name="section">
</div>
<button type="submit" class="btn btn-success">삽입하기</button>
</form>
</div>
</body>
</html>
edit.html
<!DOCTYPE html>
<html lang="en">
<head>
<title>물품관리</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container">
<h2>상품수정</h2>
<p>나는 날마다 점점 더 좋아진다.</p>
<form method="POST">
<div class="form-group">
<label for="id">아이디:</label>
<input type="text" class="form-control" id="id" name= "id" value = "<%= data.id%>" disabled>
</div>
<div class="form-group">
<label for="name">상품이름:</label>
<input type="text" class="form-control" id="name" name= "name" value = "<%= data.name%>">
</div>
<div class="form-group">
<label for="num">번호</label>
<input type="text" class="form-control" id="num" name="num" value = "<%= data.modelnumber%>">
</div>
<div class="form-group">
<label for="section">분류</label>
<input type="text" class="form-control" id="section" name="section" value = "<%= data.series%>">
</div>
<button type="submit" class="btn btn-success">수정하기</button>
</form>
</div>
</body>
</html>
detail.html
<!DOCTYPE html>
<html lang="en">
<head>
<title>물품상제</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
</head>
<body>
<div class="container">
<h2>상품상세보기</h2>
<p></p>
<form method="POST">
<div class="form-group">
<label for="id">아이디:</label>
<input type="text" class="form-control" id="id" name= "id" value = "<%= data.id%>" disabled>
</div>
<div class="form-group">
<label for="name">상품이름:</label>
<input type="text" class="form-control" id="name" name= "name" value = "<%= data.name%>" disabled>
</div>
<div class="form-group">
<label for="num">번호</label>
<input type="text" class="form-control" id="num" name="num" value = "<%= data.modelnumber%>" disabled>
</div>
<div class="form-group">
<label for="section">분류</label>
<input type="text" class="form-control" id="section" name="section" value = "<%= data.series%>" disabled>
</div>
<!-- <button type="submit" class="btn btn-success">수정하기</button> -->
</form>
</div>
</body>
</html>