JSP 게시판 만들기 No4
이번 게시글에서는 회원가입 성공시 글 목록을 보여줄 list.jsp와 detail.jsp를 설계해보도록하겠습니다.
-설계
해당 프로젝트에선 View 즉 jsp파일은 WebContent 안에 WEB-INFO 안에 넣음 으로써 브라우저상에서 직접 접근 하지 못하게 했습니다. 때문에 View 접근은 Controller로 접근해야합니다.
-구현 서블릿
ListController -> List.jsp로 접근 할 수 있도록 하기 위한 컨트롤러
ListDetailController -> 글 제목 클릭 시 글에 대한 내용을 보여 줄 수 있도록 Detail.jsp로 이동하는 컨트롤러입니다.
-기능
로그인 세션이 없으면 로그인 버튼, 로그인 세션이 있으면 글쓰기 로그아웃이 뜨도록 만들었습니다.
글 클릭 시 조회수가 증가 할 수 있도록 만들었습니다.
부트스트랩을 이용하여 페이징을 구현했습니다.
위에 검색 버튼을 넣어 ajax를 이용하여 비동기식으로 입력한 제목에 맞는 게시물을 새로고침 없이 볼 수 있게 만들어 봤습니다.
리스트는 간단하게 만들었구요!
상세보기는 이렇게 되있습니다!
제목과 작성자, 첨부된 파일이 있으면 파일 명 , 작성 내용이 보이며,
작성자의 ID과 로그인 세션의 ID가 같으면 수정하기 삭제하기가 보이고
아니면 수정 삭제 불가하도록 만들었습니다.
댓글역시 동일 합니다.
자 그럼 list.jsp 를 먼저 보겠습니다.
List.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/css/list.css">
<!-- 부트 스트랩 & 제이쿼리 부분 -->
<link
rel="stylesheet"
href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
<link
rel="stylesheet"
href="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap-theme.min.css">
<link
rel="stylesheet"
href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
<title>Document</title>
</head>
<body>
<input type="hidden" id="member" value="${login.mname}">
<div class="table-responsive">
<h1>게시판</h1>
<div class="form-group row pull-right">
<div class = "col-xs-8">
<input class="form-control" id="bwriter" onkeyup="searchFunction()" type="text" size="20" placeholder="작성자를 입력해주세요!">
</div>
<div class="col-xs-2">
<button class="btn btn-primary" onclick="searchFunction()" type="button">검색</button>
</div>
</div>
<table class="table table-bordered table-hover">
<colgroup>
<col style="width: 10%;">
<col style="width: 40%;">
<col style="width: 20%;">
<col style="width: 20%;">
<col style="width: 10%;">
</colgroup>
<thead>
<tr>
<th>번호</th>
<th>제목</th>
<th>작성자</th>
<th>작성일</th>
<th>조회수</th>
</tr>
</thead>
<tbody id="list_table">
<c:forEach var="n" items="${list}" varStatus="st" >
<tr>
<td>${st.index+1}</td>
<td><a href="/list/detail?bid=${n.bid}">${n.btitle}</a></td>
<td>${n.bwriter}</td>
<td>${n.bdate}</td>
<td>${n.hit}</td>
</tr>
</c:forEach>
</tbody>
</table>
<c:if test="${empty login}">
<div class="btn">
<button id="btn_login" class = "btn btn-primary" type="button" onclick="login()">로그인</button>
</div>
</c:if>
<c:if test="${!empty login}">
<div class="btn">
<button id="btn_logout" class = "btn btn-danger" type="button" onclick="logout()">로그아웃</button>
<button id ="btn_write" class = "btn btn-primary" type="button" onclick="location.href='/list/write'">글쓰기</button>
</div>
</c:if>
// 게시물 목차 번호가 나올 부분
<ul class="pagination">
</ul>
</div>
<script src="/js/list.js"></script>
</body>
</html>
List.css
.board {
width: 1000px;
margin: auto;
text-align: center;
}
.table-responsive h1 {
background-color: #9fd7f1;
color: #ffffff;
text-shadow: 4px 4px 0px #bdbdbd;
margin-top: 50px;
text-align: center;
margin-bottom: 50px;
font-weight: bold;
font-size: 70px;
}
#btn_login {
margin-top: 20px;
}
.pagination a {
display:inline-block;
margin-right:5px;
cursor:pointer;
height: 35px;
font-size: 15px;
}
.pagination {
width: 500px;
}
.table {
font-size: 18px;
margin-bottom: 50px;
}
.table-responsive ul {
margin : auto;
}
button {
height: 35px;
font-size: 15px;
width: 100px;
}
.btn {
margin-right: 20px;
padding: 0px;
display:inline-block;
float: right;
}
List.js
// ajax이용해서 검색 내용 가져오기
var request = new XMLHttpRequest();
function searchFunction() {
request.open("Post", "./SearchListController?bwriter="+ encodeURIComponent(document.getElementById("bwriter").value),true);
request.onreadystatechange = searchProcess;
request.send(null);
}
function searchProcess() {
var table= document.getElementById("list_table");
var tableHTML =""
console.log(request.readyState);
if(request.readyState ==4 && request.status ==200 ) {
console.log("request.responseText는 :"+request.responseText);
var obj = JSON.parse(request.responseText);
for(var i = 0; i < obj.inf.length ; i++) {
var bid = obj.inf[i]["bid"];
var order = i+1;
var btitle = obj.inf[i]["btitle"];
var bwriter= obj.inf[i]["bwriter"];
var bdate = obj.inf[i]["bdate"];
var hit = obj.inf[i]["hit"];
tableHTML+= "<tr><td>"+order+"</td>";
tableHTML+= "<td onclick='detail("+bid+")'><a>"+btitle+"</a></td>";
tableHTML+= "<td>"+bwriter+"</td>";
tableHTML+= "<td>"+bdate+"</td>";
tableHTML+= "<td>"+hit+"</td></tr>";
}
table.innerHTML =tableHTML;
}
}
// 페이징 부분
function pagination(){
var req_num_row=7; //보여줄 목록 갯수
var $tr=jQuery('tbody tr');
var total_num_row=$tr.length;
var num_pages=0;
if(total_num_row % req_num_row ==0){
num_pages=total_num_row / req_num_row;
}
if(total_num_row % req_num_row >=1){
num_pages=total_num_row / req_num_row;
num_pages++;
num_pages=Math.floor(num_pages++);
}
jQuery('.pagination').append("<li><a class=\"prev\">Previous</a></li>");
for(var i=1; i<=num_pages; i++){
jQuery('.pagination').append("<li><a>"+i+"</a></li>");
jQuery('.pagination li:nth-child(2)').addClass("active");
jQuery('.pagination a').addClass("pagination-link");
}
jQuery('.pagination').append("<li><a class=\"next\">Next</a></li>");
$tr.each(function(i){
jQuery(this).hide();
if(i+1 <= req_num_row){
$tr.eq(i).show();
}
});
jQuery('.pagination a').click('.pagination-link', function(e){
e.preventDefault();
$tr.hide();
var page=jQuery(this).text();
var temp=page-1;
var start=temp*req_num_row;
var current_link = temp;
jQuery('.pagination li').removeClass("active");
jQuery(this).parent().addClass("active");
for(var i=0; i< req_num_row; i++){
$tr.eq(start+i).show();
}
if(temp >= 1){
jQuery('.pagination li:first-child').removeClass("disabled");
}
else {
jQuery('.pagination li:first-child').addClass("disabled");
}
});
jQuery('.prev').click(function(e){
e.preventDefault();
jQuery('.pagination li:first-child').removeClass("active");
});
jQuery('.next').click(function(e){
e.preventDefault();
jQuery('.pagination li:last-child').removeClass("active");
});
}
jQuery('document').ready(function(){
pagination();
jQuery('.pagination li:first-child').addClass("disabled");
});
function login() {
alert("로그인 버튼 눌렀습니다");
location.href="/login";
}
function logout() {
alert("로그아웃 버튼 눌렀습니다");
location.href="/logout";
}
function detail(bid) {
location.href="/list/detail?bid="+bid;
}
LIst 관련 서블릿 부분
서블릿에 앞서 게시물관련 VO 와 DAO를 먼저 만들도록 하겠습니다.
BoardVo
package board.model;
import java.util.Date;
public class BoardVo {
private int bid;
private String mid; //bid.nextval()
private String btitle;
private String bcontent;
private String bwriter;
private Date bdate;
private int hit;
private String fileName;
private String fileRealName;
public int getBid() {
return bid;
}
public void setBid(int bid) {
this.bid = bid;
}
public String getMid() {
return mid;
}
public void setMid(String mid) {
this.mid = mid;
}
public String getBtitle() {
return btitle;
}
public void setBtitle(String btitle) {
this.btitle = btitle;
}
public String getBcontent() {
return bcontent;
}
public void setBcontent(String bcontent) {
this.bcontent = bcontent;
}
public String getBwriter() {
return bwriter;
}
public void setBwriter(String bwriter) {
this.bwriter = bwriter;
}
public Date getBdate() {
return bdate;
}
public void setBdate(Date bdate) {
this.bdate = bdate;
}
public int getHit() {
return hit;
}
public void setHit(int hit) {
this.hit = hit;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public String getFileRealName() {
return fileRealName;
}
public void setFileRealName(String fileRealName) {
this.fileRealName = fileRealName;
}
public BoardVo(int bid, String mid, String btitle, String bcontent, String bwriter, Date bdate, int hit,
String fileName, String fileRealName) {
super();
this.bid = bid;
this.mid = mid;
this.btitle = btitle;
this.bcontent = bcontent;
this.bwriter = bwriter;
this.bdate = bdate;
this.hit = hit;
this.fileName = fileName;
this.fileRealName = fileRealName;
}
}
BoardDAO
package board.model;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import sboard.model.SboardVO;
public class BoardDAO {
//커넥션 정보 가져오기
private Connection getConnection() {
String str = "jdbc:oracle:thin:@localhost:1521:orcl"; // 사무실 번지
Connection con = null;
try {
Class.forName("oracle.jdbc.driver.OracleDriver");
con = DriverManager.getConnection(str, "board", "b102030");
} catch (SQLException | ClassNotFoundException e) {
System.out.println("DB 연결 문제 입니다.");
}
return con;
}
// 작성된 게시물들을 가져오는 메소드
public List<BoardVo> getBoardList() {
List<BoardVo> list = new ArrayList<>();
Connection con = getConnection();
Statement stmt;
try {
stmt = con.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM board order by bid");
while(rs.next()) {
int bid =rs.getInt("bid");
String mid =rs.getString("mid");
String btitle =rs.getString("btitle");
String bcontent = rs.getString("bcontent");
String bwriter = rs.getString("bwriter");
Date bdate = rs.getDate("bdate");
int hit = rs.getInt("hit");
String fileName = rs.getString("filename");
String fileRealName = rs.getString("filerealname");
System.out.println("조회수 : "+hit);
list.add(new BoardVo (
bid,mid,btitle,bcontent,
bwriter,bdate,hit,fileName,fileRealName
));
}
stmt.close();
con.close();
rs.close();
} catch (SQLException e) {
System.out.println("select문 오류 입니다.");
}
return list;
}
// 해당 글을 클릭했을 시 그 글에 맞는 정보를 가져오는 메소드
public BoardVo getBoard(int bid) {
BoardVo board =null;
Connection con =getConnection();
PreparedStatement pstmt;
try {
pstmt = con.prepareStatement("select * from board where bid=? ");
pstmt.setInt(1, bid);
ResultSet rs = pstmt.executeQuery();
rs.next();
int hit = rs.getInt("hit");
board = new BoardVo (
rs.getInt("BID"),rs.getString("MID"),rs.getString("BTITLE"),
rs.getString("BCONTENT"),rs.getString("BWRITER"),
rs.getDate("BDATE"),rs.getInt("hit"),rs.getString("filename"),
rs.getString("filerealname")
);
rs.close();
pstmt.close();
con.close();
plushit(hit,bid);
} catch (SQLException e) {
e.printStackTrace();
}
return board;
}
// 검색 시 해당 조건에 맞는 글들을 가져오는 메소드
public ArrayList<BoardVo> searchBoard(String bwriter) {
ArrayList<BoardVo> boardList = new ArrayList<>();
Connection con =getConnection();
PreparedStatement pstmt;
try {
pstmt = con.prepareStatement("SELECT * FROM board where bwriter like ?");
pstmt.setString(1,"%"+bwriter+"%");
ResultSet rs = pstmt.executeQuery();
while(rs.next()) {
BoardVo board = new BoardVo (
rs.getInt("BID"),rs.getString("MID"),rs.getString("BTITLE"),
rs.getString("BCONTENT"),rs.getString("BWRITER"),
rs.getDate("BDATE"),rs.getInt("hit"),rs.getString("filename"),
rs.getString("filerealname")
);
boardList.add(board);
}
rs.close();
pstmt.close();
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
return boardList;
}
// 글 클릭 시 조회수를 증가 시켜주는 메소드
public void plushit(int hit,int bid) {
Connection con =getConnection();
try {
PreparedStatement pstmt =
con.prepareStatement("update board set hit=? where bid=? ");
pstmt.setInt(1, hit+1);
pstmt.setInt(2, bid);
pstmt.executeUpdate();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
// 글쓰기시 입력받은 값을 insert 해주는 메소드
public void InsertBoard(String id , String title , String content , String writer,String bdate
,String filename , String filerealname) {
Connection con =getConnection();
PreparedStatement pstmt;
try {
pstmt = con.prepareStatement("insert into board values(bid.nextval,?,?,?,?,?,?,?,?)");
pstmt.setString(1,id);
pstmt.setString(2,title);
pstmt.setString(3,content);
pstmt.setString(4,writer);
pstmt.setString(5,bdate);
pstmt.setInt(6,0);
pstmt.setString(7,filename);
pstmt.setString(8,filerealname);
pstmt.executeUpdate();
pstmt.close();
con.close();
} catch (SQLException e) {
System.out.println("본문 insert 오류 입니다.");
}
}
// 글 삭제 시 글을 삭제해주는 메소드
public void DeleteBoard(int bid) {
Connection con = getConnection();
PreparedStatement pstmt;
try {
pstmt = con.prepareStatement("delete from board where bid=?");
pstmt.setInt(1, bid);
pstmt.executeUpdate();
} catch (SQLException e) {
System.out.println("delete문 오류 입니다.");
}
}
// 글 수정시 업데이트 해주는 메소드
public void UpdateBoard(int bid, String title , String content) {
Connection con = getConnection();
PreparedStatement pstmt;
try {
pstmt = con.prepareStatement("update board set BTITLE =?,BCONTENT =? where bid=? ");
pstmt.setString(1, title);
pstmt.setString(2, content);
pstmt.setInt(3, bid);
pstmt.executeUpdate();
} catch (SQLException e) {
System.out.println("본문 업데이트 문제 입니다.");
e.printStackTrace();
}
}
// 해당 글에 달린 댓글들을 불러와주는 메소드
public List<SboardVO> getComment(int bid) {
Connection con = getConnection();
List<SboardVO> slist = new ArrayList<SboardVO>();
try {
PreparedStatement pstmt = con.prepareStatement("select * from sboard where bid=? ");
pstmt.setInt(1, bid);
ResultSet rs = pstmt.executeQuery();
while(rs.next()) {
slist.add(new SboardVO (
rs.getInt("BID"),rs.getString("MID"),rs.getInt("SID"),
rs.getString("SCONTEXT"),rs.getString("SWRITER"),
rs.getDate("SDATE")
));
}
rs.close();
pstmt.close();
con.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return slist;
}
}
ListController.java - 로그인 성공 시 해당 서블릿으로 이동하여 List.jsp를 요청 하게끔 합니다.
package list.controller;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import board.model.BoardDAO;
import board.model.BoardVo;
@WebServlet("/list")
public class ListController extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
List<BoardVo> list = new ArrayList<>();
BoardDAO dao = new BoardDAO();
list= dao.getBoardList();
System.out.println(list);
req.setAttribute("list", list);
req.getRequestDispatcher("/WEB-INF/view/List.jsp").forward(req, resp);
}
}
Detail.jsp -글의 상세 정보를 보여주는 jsp 입니다.
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/css/detail.css">
<title>Document</title>
</head>
<body>
<div class="form_wrap">
<form action="/write/try" class="write_form" method="POST">
<h1>글보기</h1>
<hr>
<p></p>
<input
value="${list.btitle}"
readonly="readonly"
name="title"
id="input_title"
type="text"
placeholder="제목">
<br>
<input
value="${list.bwriter}"
readonly="readonly"
name="write"
id="input_title"
type="text"
placeholder="작성자">
<p>
첨부된 파일 :
<a href="/downloadAciton?file=${list.fileRealName}">${list.fileRealName}</a>
</p>
<p></p>
<textarea
readonly="readonly"
name="context"
id="input_context"
cols="50"
rows="10"
placeholder="내용">${list.bcontent}</textarea>
<c:if test="${login.mid eq list.mid }">
<p></p>
<button
id="btn_update"
type="button"
onclick="location.href ='/list/update?id=${list.bid}'">수정하기</button>
<p></p>
<button
id="btn_delete"
type="button"
onclick="location.href ='/list/delete?id=${list.bid}'">삭제하기</button>
</c:if>
<p></p>
<button type="button" onclick="location.href='/list'">목록보기</button>
</form>
<form
id="comment_form"
name="comment_form"
action="/list/comment/write"
method="post">
<h2>댓글</h2>
<hr>
<div class="input">
<textarea
name="comment"
id="comment"
cols="40"
rows="2"
placeholder="댓글을 입력해주세요"></textarea>
<input type="hidden" name="bid" value="${list.bid}">
<button id="comment_write" type="submit">댓글 작성</button>
</div>
</form>
<div class="comment_wrap">
<c:foreach var="n" items="${slist}">
<div class="comment">
<p>${n.swriter}</p>
<textarea readonly="readonly" class="comment_output" cols="30" rows="1">${n.scontent}</textarea>
<c:if test="${login.mid eq n.mid }">
<form name="comment_update_form" action="/list/comment/update" method="post">
<input type="hidden" name="bid" value="${n.bid}">
<input type="hidden" name="sid" value="${n.sid}">
<input type="hidden" id="scontent" name="scontent" value="${n.scontent}">
<button type="button" onclick="comment_update()">수정</button>
<button value="${n.sid}" type="button" onclick="comment_delete(this.value)">삭제</button>
</form>
</c:if>
</div>
</c:foreach>
</div>
</div>
<script src="/js/detail.js"></script>
</body>
</html>
Detail.css
.form_wrap {
margin: auto;
width: 500px;
}
.write_form h1 {
text-align: center;
}
.write_form {
display: flex;
flex-direction: column;
width: 500px;
}
.write_form input,textarea {
font-weight: bold;
font-size: 20px;
border: none;
resize: none;
}
#input_context {
font-weight :bold;
text-align: left;
}
textarea,input {
}
.comment_form {
margin: auto;
width: 500px
}
.comment_input {
border: 1px solid black;
}
#comment {
display: inline;
font-size: 15px;
}
.comment {
border-bottom: 1px solid black;
}
.comment_output {
display: inline;
font-size: 15px;
}
.input {
border-bottom: 1px solid black;
margin-top: 10px;
display: flex;
}
.input button {
height: 25px;
}
Detail.js
function comment_delete(value) {
let check = confirm("댓글을 삭제 하시겠습니까?");
if(check ===true) {
location.href="/list/comment/delete?sid="+value;
}
}
function comment_update(value) {
let check = confirm("댓글을 수정 하시겠습니까?");
let scontent = document.getElementById('scontent');
if(check ===true) {
let comment = prompt("수정할 댓글을 입력해주세요!");
scontent.value = comment;
document.comment_update_form.submit();
}
}
ListDetailController.java
package list.controller;
import java.io.File;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import board.model.BoardDAO;
import board.model.BoardVo;
import sboard.model.SboardVO;
@WebServlet("/list/detail")
public class ListDetailController extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
int bid = Integer.parseInt(req.getParameter("bid"));
List<SboardVO> slist = new ArrayList<SboardVO>();
BoardDAO dao = new BoardDAO();
BoardVo board = dao.getBoard(bid);
slist = dao.getComment(bid);
req.setAttribute("list", board);
req.setAttribute("slist", slist);
req.getRequestDispatcher("/WEB-INF/view/Detail.jsp").forward(req, resp);
}
}
Ajax 관련 글은 삽질을 많이해서 다음 글에 정리해서 올리겠습니다!
언제나 질문 피드백은 환영입니다!