오늘이라도

[Spring] 17. 방명록 만들기 ③ : 글 상세 조회, 수정 본문

취업성공패키지 SW 개발자 교육/Spring

[Spring] 17. 방명록 만들기 ③ : 글 상세 조회, 수정

upcake_ 2020. 7. 23. 09:29
반응형

https://github.com/upcake/Class_Examples

교육 중에 작성한 예제들은 깃허브에 올려두고 있습니다. 

gif 파일은 클릭해서 보는 것이 정확합니다.


 - 방명록 만들기 ③ : 글 상세 조회, 수정 -

▲글 상세 조회, 수정 작동 화면

 

@charset "UTF-8";
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+KR&display=swap');

body {
	margin: 0 auto;
	text-align: center;
	font-size: 16px;
	font-family: 'Noto Sans KR', sans-serif;
}

a:link, a:visited {
	text-decoration: none;
	color: #000;
}

#content {
	padding: 20px 0;
	min-width: 1024px;	/* 창의 최소 크기 지정 */
}

img {
	vertical-align: middle;	/* 세로축 가운데 정렬 */
}

table {
	width: 80%;
	margin: 0 auto;
	border: 1px solid;
	border-collapse: collapse;	/* 테두리 겹침 설정 collapse: 겹치지 않게 처리 */
}

table th, table td {
	border: 1px solid;
	padding: 5px 10px;
}

table td a:hover { font-weight: bold; }

.btnSet { margin-top: 20px;	}

a.btn-fill, a.btn-empty {
	text-align: center;
	padding: 3px 10px;
	border:1px solid #3367d6;
	border-radius: 3px;
	box-shadow: 2px 2px 3px #022d72;
	/* 오른쪽, 아래쪽, 번진 정도 */
}

a.btn-fill { 
	background-color: #3367d6;
	color: #fff;
}

a.btn-empty { 
	background-color: #fff;
	color: #3367d6
}

a.btn-fill-s, a.btn-empty-s {
	text-align: center;
	padding: 1px 10px;
	border:1px solid #c4dafc
	border-radius: 3px;
	box-shadow: 2px 2px 3px #022d72;
	color: #0000cd;
	font-size: 13px;
}

a.btn-fill-s { 
	background-color: #bacdfa;
}

a.btn-empty-s { 
	background-color: #fff;
}

.btnSet a:not(:first-child) {
	margin-left: 3px;
}

a:hover { cursor:pointer; }

input {
	height: 22px;
	padding: 3px 5px;
	font-size: 15px;
}

input[type=radio] {
	width: 18px;
	margin: 0 5px 3px;
	vertical-align: middle;
}

table tr td label:not(:last-child) {
	margin-right: 20px;	
}

.w-pct60 { width: 60% }
.w-pct70 { width: 70% }
.w-pct80 { width: 80% }
.w-px40 { width: 40px }
.w-px60 { width: 60px }
.w-px80 { width: 80px }
.w-px100 { width: 100px }
.w-px120 { width: 120px }
.w-px140 { width: 140px }
.w-px160 { width: 160px }
.w-px180 { width: 180px }
.w-px200 { width: 200px }
.w-px300 { width: 300px }

.left { text-align: left }
.right { text-align: right }

.font-img { cursor: pointer; }

ul { list-style: none; padding: 0; }
#list-top { width: 80%; padding: 20px 10%;}
#list-top ul { margin:0; display:flex; }
#list-top ul:last-child { float: right; }
#list-top ul:first-child { float: left; }
#list-top ul li:not(:first-child) { margin-left:2px }
#list-top div { width: 100%; height: 32px;}
#list-top ul li * { vertical-align:middle; }

input[name=title] { width:calc(100% - 14px) }
textarea[name=content] { width:calc(100% - 6px); height: 150px; resize: none;}

/* 파일 첨부 */
.file-img { width: 18px; height:18px; cursor:poinrter; }
#attach-file, #delete-file { display:none; }

select { height: 32px }

ul.grid { width: 80%; margin: 0 10%; }
.grid li { float: left; width: 19%; height: 150px; border: 1px solid gray; box-sizing: border-box; }
.grid li:not(:nth-child(5n)) { margin: 0 1.25% 20px 0  }
.grid li div{ text-align: left; padding: 10px; }
.grid li div:nth-child(1) { 
	height:60px; 
	line-height: 23px;
	overflow:hidden; 
	display:-webkit-box;
	-webkit-line-clamp: 3;
	-webkit-box-orient: vertical; /* 말 줄임 표시 */
	word-Wrap: break-word; /* 영문 여러 줄 */
}
.grid li div:nth-child(2) { padding: 0 10px; }

▲common.css

 

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>board list jsp</title>
<style type="text/css">
table { table-layout:fixed; }
table td { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.grid li div span{ float: right; }
</style>
</head>
<body>
<h3>방명록</h3>
<form id="list" method="post" action="">
	<input type="hidden" name="curPage" value="1" />
	<input type="hidden" name="id" />
	<div id="list-top">
		<div>
			<!-- 검색 -->
			<ul>
				<li>
					<select name="search" class="w-px80">
						<option value="all" ${page.search eq 'all' ? 'selected' : '' }>전체</option>
						<option value="title" ${page.search eq 'title' ? 'selected' : '' }>제목</option>
						<option value="content" ${page.search eq 'content' ? 'selected' : '' }>내용</option>
						<option value="writer" ${page.search eq 'writer' ? 'selected' : '' }>작성자</option>
					</select>
				</li>
				<li>
					<input type="text" name="keyword" class="w-px300"/>
				</li>
				<li>
					<a class="btn-fill" onclick="$('form').submit()">검색</a>
				</li>
			</ul>
			
			<ul>
				<li>
					<select name="pageList" class="w-px80" onchange="$('[name=curPage]').val(1); $('form').submit()">
						<option value="10" ${page.pageList eq 10 ? 'selected' : '' }>10개씩</option>
						<option value="20" ${page.pageList eq 20 ? 'selected' : '' }>20개씩</option>
						<option value="30" ${page.pageList eq 30 ? 'selected' : '' }>30개씩</option>
					</select>
				</li>
				<li>
					<select name="viewType" class="w-px100" onchange="$('form').submit()">
						<option value="list" ${page.viewType eq 'list' ? 'selected' : '' }>리스트 형태</option>
						<option value="grid" ${page.viewType eq 'grid' ? 'selected' : '' }>바둑판 형태</option>
					</select>
				</li>
				<!-- 로그인되어 있으면 글쓰기 가능 -->
				<core:if test="${!empty login_info }">
					<li>
						<!-- 글쓰기 버튼 -->
						<a class="btn-fill" href="new.bo">글쓰기</a>
					</li>
				</core:if>			
			</ul>
		</div>
	</div>
</form>

<div id="data-list">
	<core:if test="${page.viewType eq 'list' }">
		<table>
			<tr>
				<th class="w-px60">번호</th>
				<th>제목</th>
				<th class="w-px100">작성자</th>
				<th class="w-px120">작성일자</th>
				<th class="w-px60">첨부 파일</th>
			</tr>
			<core:forEach items="${page.list }" var="vo">
				<tr>
					<td>${vo.no }</td>
					<td class="left"><a onclick="go_detail(${vo.id})">${vo.title }</a></td>
					<td>${vo.name }</td>
					<td>${vo.writedate }</td>
					<td>
						<core:if test="${!empty vo.filename }">
							<img src="img/attach.png" class="file-img"/>
						</core:if>
					</td>
				</tr>
			</core:forEach>
		</table>
	</core:if>
	
	<core:if test="${page.viewType eq 'grid' }">
		<ul class="grid">
			<core:forEach items="${page.list }" var="vo">
				<li>
					<div><a onclick="go_detail(${vo.id})">${vo.title }</a></div>
					<div>${vo.name }</div>
					<div>
						${vo.writedate }
						<span>${empty vo.filename ? '' : '<img src="img/attach.png" class="file-img" />' }</span>
					</div>
				</li>
			</core:forEach>
		</ul>
	</core:if>
</div>

<div class="btnSet">
	<jsp:include page="/WEB-INF/views/include/page.jsp"/>
</div>
<script type="text/javascript">
$(function(){
	$('#data-list ul').css('height', 
			( ( $('.grid li').length % 5 > 0 ? 1 : 0 ) + Math.floor($('.grid li').length / 5) )
			 * $('.grid li').outerHeight(true) - 20);
})

function go_detail(id) {
	$('[name=id]').val(id);
	$('form').attr('action', 'detail.bo');
	$('form').submit();	
}
</script>
</body>
</html>

▲list.jsp

 

package com.hanul.iot;

import java.io.File;

import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;

import board.BoardPage;
import board.BoardServiceImpl;
import board.BoardVO;
import common.CommonService;
import member.MemberVO;

@Controller
public class BoardController {
	@Autowired private BoardServiceImpl service;
	@Autowired private BoardPage page;
	@Autowired private CommonService common;
	
	//방명록 목록 화면 요청================================================================
	@RequestMapping("/list.bo")
	public String list(HttpSession session, Model model, @RequestParam(defaultValue = "1") int curPage,
			String search, String keyword, @RequestParam(defaultValue = "10") int pageList, 
			@RequestParam(defaultValue = "list") String viewType) {
		//DB에서 방명록 정보를 조회해와 목록 화면에 출력
		session.setAttribute("category", "bo");
		page.setCurPage(curPage);
		page.setSearch(search);
		page.setKeyword(keyword);
		page.setPageList(pageList);
		page.setViewType(viewType);
		
		model.addAttribute("page", service.board_list(page));
		
		return "board/list";
	} //list()
	
	//방명록 신규 화면 요청================================================================
	@RequestMapping("/new.bo")
	public String board() {
		//방명록 글쓰기 화면으로 연결
		return "board/new";
	} // board()
	
	//신규 방명록 저장 처리 요청================================================================
	@RequestMapping("/insert.bo")
	public String insert(BoardVO vo, MultipartFile file, HttpSession session) {
		//화면에서 입력한 정보를 DB에 저장한 후 목록 화면으로 연결
		if( !file.isEmpty() ) {
			vo.setFilename( file.getOriginalFilename() );
			vo.setFilepath(common.upload("board", file, session));
		}
		vo.setWriter( ((MemberVO) session.getAttribute("login_info")).getId() );
		service.board_insert(vo);
		return "redirect:list.bo";
	} //insert()
	
	//방명록 상세 화면 요청====================================================================
	@RequestMapping("/detail.bo")
	public String detail(int id, Model model) {
		//선택한 방명록 글을 DB에서 조회해와 상세 화면에 출력
		service.board_read(id);
		model.addAttribute("vo", service.board_detail(id));
		model.addAttribute("page", page);
		model.addAttribute("crlf", "\r\n");
		
		return "board/detail";
	} //detail()
	
	//방명록 상세 화면 요청====================================================================
	@ResponseBody @RequestMapping("/download.bo")
	public void download(int id, HttpSession session, HttpServletResponse response) {
		//해당 글의 첨부 파일 정보를 조회해와 다운로드한다.
		BoardVO vo = service.board_detail(id);
		common.download(vo.getFilename(), vo.getFilepath(), session, response);
	} //download()
	
	//방명록 수정 화면 요청====================================================================
	@RequestMapping("/modify.bo")
	public String modify(int id, Model model) {
		//선택한 방명록 글의 정보를 DB에서 조회해와 수정 화면에 출력
		model.addAttribute("vo", service.board_detail(id));
		return "board/modify";
	} //modify()
	
	//방명록 수정 화면 요청====================================================================
	@RequestMapping("/update.bo")
	public String update(BoardVO vo, MultipartFile file, HttpSession session, String attach, Model model) {
		//화면에서 입력한 정보를 DB에 변경, 저장한 후 상세 화면으로 연결
		BoardVO board = service.board_detail(vo.getId());
		String uuid = session.getServletContext().getRealPath("resources") + board.getFilepath();
		
		//파일을 첨부한 경우 - 없었는데 새로 첨부, 있었는데 바꿔 첨부
		if( !file.isEmpty() ) {
			vo.setFilename(file.getOriginalFilename());
			vo.setFilepath(common.upload("board", file, session));
			
			if( board.getFilename() != null ) {
				File f = new File(uuid);
				if( (f.exists()) ) { f.delete(); }
			}
		} else {
			//파일 첨부가 없는 경우 - if 없었고, else 있었는데 그대로 사용하는 경우
			if( attach.isEmpty() ) {  
				File f = new File(uuid);
				if( (f.exists()) ) { f.delete(); }
			} else {
				vo.setFilename(board.getFilename());
				vo.setFilepath(board.getFilepath());
			}
		}
		service.board_update(vo);
		
		//기존 방법
		//return "redirect:detail.bo?id=" + vo.getId();
		
		//다른 방법
		model.addAttribute("url", "detail.bo");
		model.addAttribute("id", vo.getId());
		return "board/redirect";
	}
} //class

▲BoardController.java

 

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>board detail jsp</title>
<style type="text/css">
table td { word-break:break-all; } /* 영문으로'만' 이루어진 글 줄바꿈 되게끔 처리 */
</style>
</head>
<body>
<h3>방명록 상세 조회</h3>
<table>
	<tr>
		<th class="w-px160">제목</th>
		<td class="left" colspan="5" class="left">${vo.title }</td>
	</tr>
	<tr>
		<th>작성자</th>
		<td>${vo.name }</td>
		<th class="w-px100">작성일자</th>
		<td class="w-px100">${vo.writedate }</td>
		<th class="w-px80">조회수</th>
		<td class="w-px60">${vo.readcnt }</td>
	</tr>
	<tr>
		<th>내용</th>
		<td class="left" colspan="5">${fn:replace(vo.content, crlf, '<br>') }</td>
	</tr>
	<tr>
		<th>첨부 파일</th>
		<td class="left" colspan="5">
			<core:if test="${!empty vo.filename }">
				${vo.filename }
				<a href="download.bo?id=${vo.id }"><i class="fas fa-download font-img"></i></a>
			</core:if>
		</td>
	</tr>
</table>
<div class="btnSet">
	<a class="btn-fill" onclick="go_list()">목록으로</a>
	<!-- 작성자로 로그인한 경우만 수정/삭제 가능, 관리자는 삭제 가능 -->
	<core:if test="${login_info.id eq vo.writer}"> 
		<a class="btn-fill" onclick="$('form').attr('action', 'modify.bo'); $('form').submit()">수정</a>
	</core:if>
	<core:if test="${login_info.id eq vo.writer or login_info.admin eq 'Y' }"> 
		<a class="btn-fill">삭제</a>
	</core:if>
</div>
<form method="post" action="list.bo">
	<input type="hidden" name="id" value="${vo.id }" />
	<input type="hidden" name="curPage" value="${page.curPage }" />
	<input type="hidden" name="search" value="${page.search }" />
	<input type="hidden" name="keyword" value="${page.keyword }" />
	<input type="hidden" name="viewType" value="${page.viewType }" />
	<input type="hidden" name="pageList" value="${page.pageList }" />
</form>
<script type="text/javascript">
function go_list() {
	$('form').submit();
}
</script>
</body>
</html>

▲detail.jsp

 

package board;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class BoardServiceImpl implements BoardService {
	@Autowired private BoardDAO dao;

	@Override
	public int board_insert(BoardVO vo) {
		return dao.board_insert(vo);
	}

	@Override
	public BoardPage board_list(BoardPage page) {
		return dao.board_list(page);
	}

	@Override
	public BoardVO board_detail(int id) {
		return dao.board_detail(id);
	}

	@Override
	public void board_read(int id) {
		dao.board_read(id);
	}

	@Override
	public int board_update(BoardVO vo) {
		return dao.board_update(vo);
	}

	@Override
	public int board_delete(int id) {
		// TODO Auto-generated method stub
		return 0;
	}

}

▲BoardServiceImpl.java

 

package board;

import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

@Repository
public class BoardDAO implements BoardService {
	@Autowired private SqlSession sql;
	
	@Override
	public int board_insert(BoardVO vo) {
		return sql.insert("board.mapper.insert", vo);
	}

	@Override
	public BoardPage board_list(BoardPage page) {
		page.setTotalList((Integer) sql.selectOne("board.mapper.total", page));
		page.setList(sql.selectList("board.mapper.list", page));
		return page;
	}

	@Override
	public BoardVO board_detail(int id) {
		return sql.selectOne("board.mapper.detail", id);
	}

	@Override
	public void board_read(int id) {
		sql.update("board.mapper.read", id);
	}

	@Override
	public int board_update(BoardVO vo) {
		return sql.update("board.mapper.update", vo);
	}

	@Override
	public int board_delete(int id) {
		// TODO Auto-generated method stub
		return 0;
	}

}

▲BoardDAO.java

 

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="board.mapper">
	<select id="total" resultType="integer">
		SELECT COUNT(*) FROM board <include refid="search"/>
	</select>
	
	<select id="list" resultType="board.BoardVO">
		SELECT n.*, (SELECT name FROM member WHERE member.id=writer) name
		FROM (SELECT b.*, ROWNUM no
	   	 	  FROM (SELECT * FROM board <include refid="search"/> ORDER by id) b
			  ORDER BY no DESC) n
		WHERE no BETWEEN #{beginList } AND #{endList }
					
	</select>
	
	<sql id="search">
		<if test="search == 'title' or search == 'content'" >
			WHERE ${search } LIKE '%' || #{keyword } || '%'
		</if>
		<if test="search == 'writer'" >
			WHERE <include refid="writer" />
		</if>
		<if test="search == 'all'">
			WHERE TITLE LIKE '%' || #{keyword } || '%'
			OR content LIKE '%' || #{keyword } || '%'
			OR <include refid="writer" />
		</if>
	</sql>
	
	<sql id="writer">
		writer IN (SELECT id FROM member WHERE name LIKE '%' || #{keyword } || '%')
	</sql>
	
	<insert id="insert">
		INSERT INTO board(title, content, writer, filename, filepath)
		VALUES (#{title }, #{content}, #{writer}, #{filename, jdbcType=VARCHAR}, #{filepath, jdbcType=VARCHAR} )
	</insert>
	
	<update id="read">
		UPDATE BOARD SET readcnt= readcnt + 1 WHERE id= #{id }
	</update>
	
	<select id="detail" resultType="board.BoardVO">
		SELECT b.*, (SELECT name FROM member m WHERE m.id= b.writer) name
		FROM board b 
		WHERE id=#{id }
	</select>
	
	<update id="update">
		UPDATE board SET title=#{title }, content=#{content }, filename=#{filename, jdbcType=VARCHAR}, filepath=#{filepath, jdbcType=VARCHAR}
		WHERE id=#{id }
	</update>
</mapper>

▲board-mapper.xml

 

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>board modify jsp</title>
</head>
<body>
<h3>방명록 수정</h3>
<form method="post" action="update.bo" enctype="multipart/form-data">
	<table>
		<tr>
			<th class="w-px160">제목</th>
			<td><input type="text" name="title" value="${vo.title }" class="need" title="제목"/></td>
		</tr>
		<tr>
			<th>작성자</th>
			<td>${vo.name }</td>
		</tr>
		<tr>
			<th>내용</th>
			<td><textarea name="content" class="need" title="내용">${vo.content }</textarea></td>
		</tr>
		<tr>
			<th>첨부 파일</th>
			<td class="left">
				<label>
					<input type="file" name="file" id="attach-file" />
					<img src="img/select.png" class="file-img" />
				</label>
				<span id="file-name">${vo.filename }</span>
				<span id="delete-file" style="color:red"><i class="fas fa-times font-img"></i></span>
			</td>
		</tr>
	</table>
	<input type="hidden" name="attach"/>
	<input type="hidden" name="id" value="${vo.id }" />
</form>
<div class="btnSet">
	<a class="btn-fill" onclick="if( necessary() ) { $('[name=attach]').val( $('#file-name').text() ); $('form').submit(); }">저장</a>
	<a class="btn-empty" href="javascript:history.go(-1)">취소</a>
</div>
<script type="text/javascript" src="js/file_attach.js"></script>
<script type="text/javascript" src="js/need_check.js"></script>
<script type="text/javascript">
if(${!empty vo.filename}) {
	$('#delete-file').css("display", "inline");
}
</script>
</body>
</html>

▲modify.jsp

 

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<form method="post" action="${url }">
<input type="text" name="id"  value="${id }"/>
</form>
<script>
$('form').submit();
</script>

▲redirect.jsp

 

 

 

반응형