전체 글 목록을 읽어오는 기능과 페이징 처리 기능을 넣었으니
새로 글쓰기, 상세 데이터 조회, 글 수정, 글 삭제 기능을 구현해서 CRUD 풀셋을 완성할 차례다.
처음 밝혔듯이 원래는 상세 조회, 수정, 삭제, 입력을 모두 하나의 jsp 에서 처리할 수 있게 구현하려고 했으나...
구체적인 설계 계획없이 일단 만들기 시작하다가 꼬이면서 수정, 입력을 각각의 별도 화면에서 처리하는 것으로 바꿨다.
1. 글 신규 작성 + CKEditor 삽입
목록 조회 화면에서 글 쓰기 버튼을 클릭하면 loaction.href 를 이용하여 글 작성 화면으로 이동한다
$("#toWrite").click(function(){
location.href="/home/note/writeNoteView";
});
//게시물 작성 화면
@RequestMapping("/writeNoteView")
public String writeNoteView() throws Exception {
return "/note/writeNote";
}
//게시물 입력
@RequestMapping("/writeNote")
public @ResponseBody String writeNote(NoteVO vo) throws Exception {
String rtn = "";
int result = svc.writeNote(vo);
if(result > 0) rtn = "New Note is saved!";
else rtn = "New Note cannot be saved";
return rtn;
}
그냥 멋없게 글쓰기 영역을 만들고 싶지 않아서 다른 똑똑한 분들이 만들어낸 에디터를 심어주기로 했다....
평소에는 네이버 스마트에디터를 썼는데 이번에 CKEditor를 삽입
(CKEditor 다운로드 링크 : https://ckeditor.com/ckeditor-4/download/)
Standard package를 받아서 webapp > resources 밑에 넣었다.
jsp에서 CKEditor를 써줄거라고 선언을 해준 다음
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page isELIgnored="false" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>My Home</title>
<script type="text/javascript" src="/home/resources/js/jquery-3.2.1.min.js"></script>
<!-- 부트스트랩 -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap-theme.min.css">
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js"></script>
<script src="/home/resources/js/note/writeNote.js"></script>
<link rel="stylesheet" href="/home/resources/css/writeNote.css">
<!-- CKEditor -->
<script src="//cdn.ckeditor.com/4.8.0/standard/ckeditor.js"></script>
</head>
<body>
<jsp:include page="/WEB-INF/views/header.jsp" />
<div id="location" class="jumbotron">
<h2>WRITE NOTES</h2>
<h4> - How to make this website</h4>
</div>
<div id="main">
<div id="writing">
<form action="writeNote" method="POST" name="newNoteForm">
<div class="input-group input-group-lg">
<span class="input-group-addon" id="sizing-addon1">Title </span>
<input name="title" id="title" type="text" class="form-control" aria-describedby="sizing-addon1" required style="width:100%;">
</div>
<br>
<textarea name="contents" id="contents" rows="10"></textarea>
</form>
</div>
<br>
<br>
<div id="btns">
<button id="writeBtn" class="btn btn-primary">Write</button>
<button class="btn btn-default" onclick="history.go(-1)">Back</button>
</div>
</div>
</body>
</html>
js에서도 선언해주기만 하면 끝
이미지 업로드를 하려면 좀 더 소스를 건드려야 하지만 일단 에디터를 화면에 집어넣는 것까지만 했다.
새 글 작성에 성공하면 팝업창으로 결과 메시지를 보여준 다음 목록 조회 화면으로 돌아가게 했다.
$(function(){
CKEDITOR.replace( 'contents', {
width : '100%',
height : '500px',
});
$("#writeBtn").on("click", function(){
var params = {
title : $("#title").val(),
contents : CKEDITOR.instances.contents.getData()
};
//ajax 호출
$.ajax({
url : "writeNote",
type : "post",
data : params,
success : function(data){
alert(data);
location.href = "/home/note/noteListView";
},
error : function(data){
alert(data);
console.log("AJAX_ERROR");
}
});
});
});
순번을 생성할 때 트리거를 쓰지 않고 아래와 같이 셀렉트 쿼리를 써서 처리했다.
<!-- 새 글 작성 -->
<insert id="writeNote" parameterType="note">
INSERT INTO NOTE
VALUES
(
(SELECT LPAD(MAX(SN) + 1, '5', '0') AS SN
FROM NOTE AS TB_TEMP),
#{title},
#{contents},
NOW(),
NOW(),
'N'
)
</insert>
쿼리를 보면 서브 쿼리에서 쓴 NOTE 에 alias를 줬다...
Maria DB에서 저 부분에 별명을 주지 않고 실행할 경우 아래와 같은 에러가 발생하기 때문이다.
Error Code: 1093. Table 'NOTE' is specified twice, both as a target for 'INSERT' and as a separate source for data
오라클이나 티베로에서는 동일 테이블을 대상으로 서브 쿼리를 써도 처리가 되지만 Maria DB에서는 이 기능을 지원하지 않는 듯 하다.
처음엔 좀 당황스러웠지만 Alias 만 추가해주면 아무 문제없이 돌아가니 안심하고 다음 기능을 구현하러 넘어간다.
2. 상세 조회
noteList.js 에서 ajax를 통해 목록 조회 결과를 화면에 뿌릴 때 a 태그를 써서 상세 화면으로 넘어가는 링크를 걸었다.
if(data.length > 0){
for(var i in data){
result += "<tr><td>" + data[i].sn + "</td>";
result += "<td><a href='readNote?sn=" + data[i].sn + "'>" + data[i].title + "</a></td>";
result += "<td>" + data[i].insert_dt + "</td></tr>";
}
}
파라미터를 던지는 부분까지 앞에서 만들어놨으므로 파라미터를 받는 부분부터 구현 시작
//게시물 상세 데이터 조회
@RequestMapping("/readNote")
public String readNoteView(Model model, @RequestParam(value="sn") String sn) throws Exception {
NoteVO vo = svc.retrieveNote(sn);
model.addAttribute("vo", vo);
return "/note/readNote";
}
서비스, DAO 단에서는 특별한 처리를 한 것이 없으므로 생략
쿼리는 아래와 같다.
<!-- 게시물 상세 조회 -->
<select id="retrieveNote" parameterType="java.lang.String" resultType="note">
SELECT SN,
TITLE,
CONTENTS
FROM NOTE
WHERE SN = #{sn}
AND DELETE_AT = 'N'
</select>
관리자로 로그인했을 때만 글쓰기 버튼과 삭제 버튼이 보이도록 해야할텐데 앞에서 몇번 해봤다고 안했다....
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page isELIgnored="false" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>My Home</title>
<script type="text/javascript" src="/home/resources/js/jquery-3.2.1.min.js"></script>
<!-- 부트스트랩 -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap-theme.min.css">
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js"></script>
<script src="/home/resources/js/note/readNote.js"></script>
<link rel="stylesheet" href="/home/resources/css/readNote.css">
</head>
<body>
<jsp:include page="/WEB-INF/views/header.jsp" />
<div id="location" class="jumbotron">
<h2>READ NOTES</h2>
<h4> - How to make this website</h4>
</div>
<div id="main">
<div id="reading">
<input type="hidden" id="sn" value="${vo.sn}">
<br>
<div id="noteTitle">
${vo.title}
</div>
<br>
<div id="noteContents">
${vo.contents }
</div>
</div>
<br><br>
<div id="btns">
<input class="btn btn-default" type="button" value="Update" id="toWrite">
<input class="btn btn-default" type="button" value="Delete" id="delBtn">
<input class="btn btn-default" type="button" value="Back" onclick="history.go(-1)">
</div>
</div>
</body>
</html>
@charset "UTF-8";
#location {
font-weight : bold;
padding : 5%;
}
#main {
padding : 0 5% 0 5%;
}
#reading {
display : block;
}
#noteTitle {
display : block;
font-weight : bold;
font-size : 30px;
text-decoration: underline;
text-shadow : 1px 1px #87837e;
}
#noteContents {
display : block;
background-color : #eef2f2;
height : 500px;
}
#btns {
display :block;
text-align : center;
}
$(function(){
$("#toWrite").click(function(){
console.log("updateNote");
updateNoteView();
});
$("#delBtn").click(function(){
deleteNote();
});
});
function updateNoteView(){
var choice = confirm("Want to revise this note?");
if(choice){
var form = document.createElement("form");
var el = document.createElement("input");
form.method = "POST";
form.action = "updateNoteView";
el.value = $("#sn").val();
el.name = "sn";
form.appendChild(el);
document.body.appendChild(form);
form.submit();
}else{
return;
}
}
function deleteNote(){
var choice = confirm("Are you sure to delete this note?");
if(choice) {
var form = document.createElement("form");
var el = document.createElement("input");
form.method = "POST";
form.action = "deleteNote";
el.value = $("#sn").val();
el.name = "sn";
form.appendChild(el);
document.body.appendChild(form);
form.submit();
}else{
return;
}
}
3. 글 삭제
상세 조회화면에서 삭제 버튼을 누르면 파라미터를 던지는 부분까지 구현되어 있으므로 그 다음부터 구현 시작
@RequestMapping("/deleteNote")
public String deleteNote(@RequestParam(value="sn") String sn) throws Exception {
String rtn = "";
int result = svc.deleteNote(sn);
if(result > 0) rtn = "/note/noteList";
else rtn = "fail";
return rtn;
}
삭제 시에는 물리 삭제를 하지 않고 논리 삭제를 하기로 했기 때문에
쿼리 역시 delete 문을 쓰지 않고 update 문을 써서 삭제 여부 컬럼에 데이터를 'Y' 로 바꿔준다.
<!-- 게시물 삭제 -->
<update id="deleteNote" parameterType="java.lang.String">
UPDATE NOTE
SET UPDATE_DT = NOW(),
DELETE_AT = 'Y'
WHERE SN = #{sn}
</update>
4. 글 수정
css는 신규 작성 화면과 동일하게 넣어서 생략.....
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page isELIgnored="false" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>My Home</title>
<script type="text/javascript" src="/home/resources/js/jquery-3.2.1.min.js"></script>
<!-- 부트스트랩 -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap-theme.min.css">
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js"></script>
<script src="/home/resources/js/note/updateNote.js"></script>
<link rel="stylesheet" href="/home/resources/css/writeNote.css">
<!-- CKEditor -->
<script src="//cdn.ckeditor.com/4.8.0/standard/ckeditor.js"></script>
</head>
<body>
<jsp:include page="/WEB-INF/views/header.jsp" />
<div id="location" class="jumbotron">
<h2>UPDATE NOTES</h2>
<h4> - How to make this website</h4>
</div>
<div id="main">
<div id="writing">
<form action="writeNote" method="POST" name="newNoteForm">
<input type="hidden" id="sn" name="sn" value="${vo.sn}" />
<div class="input-group input-group-lg">
<span class="input-group-addon" id="sizing-addon1">Title </span>
<input value="${vo.title}" name="title" id="title" type="text" class="form-control" aria-describedby="sizing-addon1" required style="width:100%;" />
</div>
<br>
<textarea name="contents" id="contents" rows="10">${vo.contents}</textarea>
</form>
</div>
<br>
<br>
<div id="btns">
<button id="writeBtn" class="btn btn-primary">Write</button>
<button class="btn btn-default" onclick="history.go(-1)">Back</button>
</div>
</div>
</body>
</html>
글을 새로 입력할 때와는 다르게 글 수정에 성공하면 팝업창을 통해서 결과 메시지만 뿌리고 화면 이동은 하지 않게 했다.
$(function(){
CKEDITOR.replace( 'contents' );
CKEDITOR.config.height = 500;
$("#writeBtn").on("click", function(){
var params = {
sn : $("#sn").val(),
title : $("#title").val(),
contents : CKEDITOR.instances.contents.getData()
};
$.ajax({
url : "updateNote",
type : "post",
data : params,
dataType : "json",
success : function(data){
alert(data);
},
error : function(data){
alert(data);
console.log("AJAX_ERROR");
}
});
});
});
//게시물 수정
@RequestMapping("/updateNote")
public @ResponseBody String updateNote(NoteVO vo) throws Exception {
String rtn = "";
int result = svc.updateNote(vo);
if(result > 0) rtn = "Your note is updated!";
else rtn = "Your note cannot be updated";
return rtn;
}
<!-- 게시물 수정 -->
<update id="updateNote" parameterType="note">
UPDATE NOTE
SET TITLE = #{title},
CONTENTS = #{contents},
UPDATE_DT = NOW()
WHERE SN = #{sn}
</update>
이렇게 해서 간단한 게시판을 달고 있는 쪼그마한 웹사이트를 만들어봤다.
사이트 구현도 간단한 거니까 구체적인 설계가 필요없을 것이라고 생각했고,
사이트 구현 과정에 대한 포스팅 역시 그냥 되는대로 막 썼더니 굉장히...내가 생각했던 것과 다른 결과물이 나왔다...
역시 계획은 중요하다는 것을 느끼며 첫번째 미니 프로젝트 종료!