각 영역에 대한 처리와 테스트가 완료 되었습니다.
화면에는 JSP와 JavaScript(jQuery), CSS, HTML을 이용해서 작성 합니다.
예제에서 사용할 디자인은 SB Admin2를 사용합니다.
(https://startbootstrap.com/template/sb-admin)
부트스트렙 페이지에서 다운 받을 수있는데 저는 예전에 받아 놓은 파일로 진행 해보겠습니다.
(지금은 템플릿이 업데이트 되어 해당 템플릿은 없는것 같습니다.(23.03.26 검색))
(여기에서 받을 수 있습니다 https://cafe.naver.com/gugucoding/3680)
11.1 목록 페이지 작업과 includes
스프링 MVC의 JSP를 처리하는 설정은 servlet-context.xml에 아래와 같이 작성 되어 있습니다.
스프링 MVC 설정에서 화면 설정은 ViewResolver 라는 객체를 통해서 이루어지는데, 위의 설정을 보면 /WEB-INF/views 폴더를 이용하는 것을 볼 수 있습니다.
/WEB-INF 경로는 브라우저에서 직접 접근할 수 없는 경로이므로 반드시 Controller를 이용하는 모델2방식에서는 기본적으로 사용하는 방식입니다.
게시물 리스트의 URL은 /board/list이므로 최종적으로 /WEB-INF/views/board/list.jsp가 됩니다.
해당 경로에 list.jsp 파일을 추가 합니다.
<%@ page language="java" contentType="text/html; charset=EUC-KR"
pageEncoding="EUC-KR"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="EUC-KR">
<title>Insert title here</title>
</head>
<body>
<h1>List Page</h1>
</body>
</html>
list.jsp가 정상적으로 호출 되는지를 확인 해야 하므로 Tomcat을 실행 합니다.
ex02가 실행 될 톰켓을 하나 만들고 http://localhost:8080/controller/board/list 로 접근 해봅니다.
Tomcat이 Eclipse 상에서 실행될때는 / 경로로 설정 되어 있지 않고 기본적으로 controller 경로를 가지게 됩니다.
기본 경로를 / 로 지정 해보겠습니다.
Contoller를 제거하고 OK를 누른 후 저장(Ctrl + s) 해 줍니다.
톰켓을 재시작 하고, http://localhost:8080/board/list 가 정상적으로 호출되는지 확인 합니다
11.1.1 SB Admin2 페이지 적용 하기
정상적으로 /board/list 페이지가 동작한다면 SB Admin2의 pages 폴더에 있는 tables.html의 내용을 list.jsp의 내용으로 그대로 복사해서 수정하고 실행 합니다.
아직 CSS, JS가 설정 되어 있지 않아 텍스트만 출력 되게 됩니다.
CSS와 JS파일들의 경로를 수정하는 작업은 브라우저의 개발자 도구를 위해 확인이 간으 하며 진행 합니다.
개발자 도구를 통해 현재 브라우저의 Network 부분을 확인하고 페이지를 새로고침하면 잘못된 URL 정보를 아래와 같이 확인 할 수 있습니다.
(개발자 도구는 F12로 실행 할 수 있습니다)
SB Admin2 의 CSS 경로는 http://localhost:8080/vendor/bootstrap/css/boostrap.min.css 경로이므로 현재 프로젝트에서는 제대로 된 서비스가 될 수 없습니다.
org.zerock.config.WebConfig 클래스에는 CSS나 JS 파일과 같이 정적인(static) 자원들의 경로를 resources 라는 경로로 지정 하고 있습니다.
<!--servlet-context.xml 의 일부-->
<resources mapping="/resources/**" location="/resources/" />
SB Admin2의 압축을 풀어 모든 폴더를 프로젝트 내 webapp 밑의 resources 폴더로 복사해 넣습니다.
파일들을 resources경로로 넣어도 아직은 페이지에서 경로를 수정하지 않았기 때문에 문제가 생깁니다.
list.jsp 파일에서 CSS나 JS파일의 경로를 /resources로 시작하도록 수정 합니다.
(단축키 Ctrl + F를 이용해서 Find/Replace를 사용 하겠습니다.)
수정 후 브라우저를 통해 /board/list를 호출하면 다음과 같이 CSS가 정상적으로 적용 된 화면을 볼 수 있습니다.
CSS, JS 파일들의 링크는 모든 페이지에서 사용될 것이므로 화면에서 디자인이 꺠지지 않는 것을 반드시 확인 해야 합니다.
11.1.2 includes 적용
JSP를 작성할 떄마다 많은 양의 HTML 코드를 이용하는 것을 막기위해 JSP include 지시자를 활용해서 페이지 제작 시에 필요한 내용만을 작성할 수 있게 사전에 작업을 해야 합니다.
현재 프로젝트 views 폴더에 includes 폴더를 만들고 header.jsp와 footer.jsp를 선언 합니다.
header.jsp 적용
header.jsp는 페이지에서 핵심적인 부분이 아닌 영역중에서 위쪽의 HTML 내용을 처리하기 위해서 작성 합니다.
브라우저에서 검사 기능을 활용하면 특정한 <div>가 어떤 부분을 의미하는지 확인 할 수 있습니다.
SB Admin2는 <div>들 중에서 id 속성값이 page-wrapper 부터가 핵심적인 페이지의 내용이므로 list.jsp 파일의 처음부분에서 <div id="page-wrapper'>라인까지 잘라서 header.jsp의 내용으로 복사 합니다.
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@include file="../includes/header.jsp" %>
header.jsp를 include한 이후 화면이 제대로 출력 되는지 확인 합니다.
footer.jsp 적용
<div id="page-wrapper">가 끝나는 태그부터 마지막까지는 footer.jsp의 내용으로 작성 합니다.
list.jsp에도 include를 추가 해줍니다.
header.jsp와 마찬가지로 수정한 후에는 브라우저에서 정상적으로 실행 되는지 확인 합니다.
11.1.3 jQuery 라이브러리 변경
JSP 페이지를 작성하다보면 JavaScript로 브라우저 내에서의 조작이 필요한 경우가 많습니다.
예제는 jQuery를 이용할 것인데, 위 방식으로 처리 했을때 jQuery 라이브러리가 footer.jsp에 포함되어 있습니다.
jQuery를 header.jsp에 선언해 두면 작성하는 JSP에서 자유롭게 사용할 수 있으므로 수정 합니다.
footer.jsp 상단의 jquery.min.js파일의 <script> 태그를 제거 합니다.
jQuery는 인터넷을 통해 다운 받을 수 있게 jquery의 링크를 검색해서 header.jsp내에 추가 합니다.
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
반응형 웹 처리
SB Admin2는 반응형으로 설계되어 있어서 브라우저의 크기에 맞게 모바일용 으로 자동으로 변경 되지만 jQuery의 최신 버전을 사용한 상태에서는 모바일 크기에서 새로고침시 메뉴가 펼쳐지는 문제가 있습니다.
이 문제를 해결하기 위해 footer.jsp 아래에 아래 코드를 추가 해줍니다.
<script>
$(document).ready(function() {
$('#dataTables-example').DataTable({
responsive: true
});
$(".sidebar-nav")
.attr("class","sidebar-nav navbar-collapse collapse")
.attr("aria-expanded",'false')
.attr("style","height:1px");
});
</script>
11.2 목록 화면 처리
list.jsp페이지의 일부를 include하는 방식으로 처리 했음에도 많은 HTML의 내용들이 존재하므로 아래와 ㅏㄱㅌ이 최소한의 태그들만 적용 시킵니다.
list.jsp에는 JSTL의 출력과 포멧을 적용할 수 있는 태그 라이브러리를 추가 합니다.
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@include file="../includes/header.jsp"%>
<div class="row">
<div class="col-lg-12">
<h1 class="page-header">Tables</h1>
</div>
<!-- /.col-lg-12 -->
</div>
<!-- /.row -->
<div class="row">
<div class="col-lg-12">
<div class="panel panel-default">
<div class="panel-heading">
Board List Page
</div>
<!-- /.panel-heading -->
<div class="panel-body">
<table width="100%" class="table table-striped table-bordered table-hover" >
<thead>
<tr>
<th>#번호</th>
<th>제목</th>
<th>작성자</th>
<th>작성일</th>
<th>수정일</th>
</tr>
</thead>
</table>
</div>
<!-- /.panel-body -->
</div>
<!-- /.panel -->
</div>
<!-- /.col-lg-12 -->
</div>
<!-- /.row -->
<%@include file="../includes/footer.jsp"%>
수정된 list.jsp를 저장하고 브라우져를 통해 원하는 형태로 출력 되었는지 확인 합니다.
11.2.1 Model에 담긴 데이터 출력
/board/list를 실행했을 때 이미 BoardController는 Model을 이용해서 게시물의 목록을 list라는 이름으로 담아서 전달 했으므로 list.jsp에서는 이를 출력만 해주면 됩니다.
출력은 JSTL을 이용해 처리 합니다.
list.jsp 내에 <tbody> 태그와 <tr>을 아래와 같이 작성 합니다.
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>
<%@include file="../includes/header.jsp"%>
<div class="row">
<div class="col-lg-12">
<h1 class="page-header">Tables</h1>
</div>
<!-- /.col-lg-12 -->
</div>
<!-- /.row -->
<div class="row">
<div class="col-lg-12">
<div class="panel panel-default">
<div class="panel-heading">
Board List Page
</div>
<!-- /.panel-heading -->
<div class="panel-body">
<table width="100%" class="table table-striped table-bordered table-hover" >
<thead>
<tr>
<th>#번호</th>
<th>제목</th>
<th>작성자</th>
<th>작성일</th>
<th>수정일</th>
</tr>
</thead>
<c:forEach items="${list}" var="board">
<tr>
<td><c:out value="${board.bno}" /></td>
<td><c:out value="${board.title}"/></td>
<td><c:out value="${board.writer}" /></td>
<td><fmt:formatDate pattern="yyyy-MM-dd" value="${board.regdate}" /></td>
<td><fmt:formatDate pattern="yyyy-MM-dd" value="${board.updateDate}" /></td>
</tr>
</c:forEach>
</table>
</div>
<!-- /.panel-body -->
</div>
<!-- /.panel -->
</div>
<!-- /.col-lg-12 -->
</div>
<!-- /.row -->
<%@include file="../includes/footer.jsp"%>
브라우저를 통해 데이터베이스의 내용을 가져오는것을 확인 할 수 있습니다.
11.3 등록 입력 페이지와 등록 처리
게시물의 등록 작업은 POST방식으로 처리하지만, 화면에서 입력을 받아야 하므로, GET 방식으로 입력 페이지를 볼 수 있도록 BoardController에 메소드를 추가 합니다.
//BoardController.java에 추가
@GetMapping("/register")
public void register() {
}
register()는 입력 페이지를 보여주는 역할만 하므로 별도의 처리가 필요하지 않습니다.
view 폴더에는 includes를 적용한 입력페이지를 작성 합니다.
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>
<%@include file="../includes/header.jsp"%>
<div class="row">
<div class="col-lg-12">
<h1 class="page-header">Board Register</h1>
</div>
<!-- /.col-lg-12 -->
</div>
<!-- /.row -->
<div class="row">
<div class="col-lg-12">
<div class="panel panel-default">
<div class="panel-heading">Board Register</div>
<!-- /.panel-heading -->
<div class="panel-body">
<form role="form" action="/board/register" method="post">
<div class="form-group">
<label>Title</label> <input class="form-control" name='title'>
</div>
<div class="form-group">
<label>Text area</label>
<textarea class="form-control" rows="3" name='content'></textarea>
</div>
<div class="form-group">
<label>Writer</label> <input class="form-control" name='writer'>
</div>
<button type="submit" class="btn btn-default">Submit
Button</button>
<button type="reset" class="btn btn-default">Reset Button</button>
</form>
</div>
<!-- end panel-body -->
</div>
<!-- end panel-body -->
</div>
<!-- end panel -->
</div>
<!-- /.row -->
<%@include file="../includes/footer.jsp"%>
register.jsp 페이지에서는 <form> 태그를 이용해서 필요한 데이터를 전송 합니다.
<input> 이나 <textarea> 태그의 name은 BoardVO 클래스의 변수와 일치시켜 줍니다.
브라우저를 통해 /board/register 화면이 제대로 출력 되는지 확인 합니다
화면이 정상적으로 보인다면 입력 항목을 넣어 새로운 게시물이 등록 되는지 확인 합니다.
BoardController의 POST방식으로 동작하는 register()는 redirect 시키는 방식을 이용하므로 게시물의 등록 후에는 /board/list로 이동하게 됩니다.
게시물이 등록은 되지만 한글이 깨지는 문제가 발생하게 됩니다.
11.3.1 한글 문제와 UTF-8 필터 처리
새로운 게시물을 등록했을때 만일 한글 입력에 문제가 있는 것을 발견했다면
- 브라우저에서 한글이 깨져서 전송 되는지 확인
- 1번이 문제가 없다면 스프링 MVC쪽에서 한글을 처리하는 필터 등록
브라우저에서 전송되는 데이터는 개발자도구를 통해 확인할 수 있습니다.
개발자도구의 NetWork를 열어 둔 상태에서 데이터를 보면 해당 내용을 볼 수 있으므로 이 때 POST 방식으로 제대로 전송 되었는지, 한글이 깨지진 않았는지 확인할 수 있습니다.
위 화면을 보면 브라우저가 한글을 문제없이 보냈음을 확인 할 수 있습니다.
그럼 문제는 Controller나 데이터베이스쪽임을 확인 할 수 있습니다.
BoardController와 BoardServiceImpl을 개발할 떄는 이미 Lookbok의 로그를 이용하여 필요한 기능을 기록해 두었으므로 이를 확인해봐야 합니다.
로그를 보면 BoardController에 전달될 떄 이미 한글이 꺠진 것을 볼 수 있습니다.
이 문제를 해결하기 위해 web.xml에 아래 내용을 추가 합니다
<filter>
<filter-name>encoding</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encoding</filter-name>
<servlet-name>appServlet</servlet-name>
</filter-mapping>
한글 처리가 끝난 후 다시 게시물을 작성해보면 문제없이 입력 되는 것을 볼 수 있습니다.
11.3.2 재전송(redirect)처리
등록 과정에서 POST방식으로 데이터가 처리되는 과정을 그림으로 표현하면 아래와 같습니다.
BoardController에서 register() 메서드는 "redirect:/board/list"를 전송하는데 브라우저는 이를 통보 받은 후 /board/list로 이동하게 됩니다.
만일 위와 같이 재전송을 하지 않는다면 사용자는 브라우저의 새로고침을 통해서 동일한 내용을 계속 서버에 등록할 수 있기 때문에 중복된 데이터가 계속 입력되는 문제가 발생하게 됩니다.
브라우저에서는 이런 경우 경고창을 보여주기는 하지만 근본적으로 차단하지는 않습니다.
따라서 등록,수정,삭제 작업은 처리가 완료된 후 동일한 내용을 전송할 수 없더록 URL을 이동하는 방식을 사용합니다.
이러한 과정에서 신경 써야 하는 부분은 등록, 수정, 삭제의 결과를 바로 알 수 있게 피드백을 줘야 한다는 점입니다.
경고창이나 <div>를 이용하는 모달창을 이용해서 이러한 작업을 처리 합니다.
BoardController에서 redirect 처리를 할 떄 RedirectAttributes라는 특별한 타입의 객체를 이용했습니다.
addFlashAttribute()의 경우 이러한 처리에 적합한데, 그 이유는 일회성으로만 데이터를 전달하기 떄문입니다.
addFlashAttribute()로 보관된 데이터 처리는 단 한번만 사용할 수 있게 보관 됩니다.(내부적으로는 HttpSession을 이용해서 처리)
list.jsp 페이지 아래쪽에 <script>를 이용해서 상황에 따른 메세지를 확인 할 수 있습니다.
<script type="text/javascript">
$(document).ready(function()){
var result = '<c:out value="${result}"/>';
});
</script>
새로운 게시물의 번호는 addFlashAttribute()로 저장되었기 때문에 한 번도 사용된 적이 없다면 위와 같은 값을 만들어 내지만 새로고침을 통해 호출하는 경우는 아래와 같이 아무런 내용이 없게 됩니다.
addFlashAttribute()를 이용해서 일회성으로만 데이터를 사용할 수 있으므로 이를 이용해서 경고창이나 모달창등을 보여주는 방식을 이용 할 수 있습니다.
11.3.3 모달(Modal)창 보여주기
최근 브라우저에서 경고창(aler)을 띄워주는 방식보다 모달창(Modal)을 보여주는 방식을 많이 사용 합니다.
BootStrap은 모달창을 간단하게 사용할 수 있으므로 목록화면에서 필요한 메세지를 보여주는 방식을 사용해보겠습니다.
모달창은 기본적으로 <div>를 화면에 특정 위치에 보여주고, 배경이되는 <div>에 배경색을 입혀 처리 합니다.
모달창은 활성화된 <div>를 선택하지 않고는 다시 원래의 화면을 볼 수 없도록 막기 때문에 메세지를 보여주는데 효과적인 방식입니다.
모달창에 대한 코드는 다운로드한 SBAdmin2의 pages 폴더내 notifications.html 파일을 참고하면 됩니다.
모달창을 처리하기 위해 우선 <div>를 이용해서 페이지의 코드에 추가합니다.
list.jsp내에 <table>태그의 아래쪽에 모달창의 <div>를 추가 합니다.
<!-- Modal 추가 -->
<div class="modal fade" id="myModal" tabindex="-1" role="dialog"
aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"
aria-hidden="true">×</button>
<h4 class="modal-title" id="myModalLabel">Modal title</h4>
</div>
<div class="modal-body">처리가 완료되었습니다.</div>
<div class="modal-footer">
<button type="button" class="btn btn-default"
data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary">Save
changes</button>
</div>
</div>
<!-- /.modal-content -->
</div>
<!-- /.modal-dialog -->
</div>
<!-- /.modal -->
모달창을 보여주는 작업은 jQuery를 이용해 처리 합니다.
function checkModal(result){
if(result ===''){
return;
}
if(parseInt(result)>0){
$(".modal-body").html("게시글" + parstInt(result) + "번이 등록 되었습니다.");
}
$("#myModal").modal("show");
}
checkModal함수는 파라미터에 따라서 모달창을 보여주거나 내용을 수정한 뒤 보이도록 작성합니다.
checkModal()에서는 새로운 게시글이 작성되는 경우 RedirectAttributes로 게시물의 번호가 전송되므로 이를 이용해 모달창의 내용을 수정 합니다.
$("#modal").modal("show")를 호출하면 위의 화면처럼 모달창이 보이게 됩니다.
이제 /board/register를 이용해 새로운 게시글을 작성하고 나면 자동으로 /board/list로 이동하면서 모달창이 보이게 됩니다.
11.3.4 목록에서 버튼으로 이동하기
게시물의 작성과 목록 페이지로 이동이 정상적으로 동작 했다면, 마지막으로 목록 페이지 상단에 버튼을 추가해서 등록 작업을 할 수 있게 처리 하겠습니다.
list.jsp의 구조를 아래와 같이 수정 합니다.
<!-- list.jsp에 아래 코드 추가-->
<button id ="regBtn" type="button" class="btn btn-xs pull-right">Register New Board</button>
list.jsp 하단의 jQuery를 이용하여 Register New Board버튼을 클릭했을때의 동작을 정의 합니다.
$("#regBtn").on("click", function(){
self.location="/board/register";
});
화면에서 Register New Board 버튼을 클릭 하면 게시물 등록 페이지로 이동 하는 것을 확인할 수 있습니다.
11.4 조회 페이지와 이동
게시물의 등록과 리스트 처리가 끝났다면 가장 중요한 틀은 완성 되었다고 볼 수 있습니다.
다음으로는 목록 페이지에서 링크를 통해 GET 방식으로 특정한 게시물을 조죄할 수 있는 기능을 작성 합니다.
11.4.1 조회 페이지 작성
조회 페이지는 입력 페이지와 거의 유사하지만 게시물 번호(bno)가 출력된다는 점과 모든 데이터가 읽기 전용으로 처리된다는 점이 가장 큰 차이 입니다.
게시물의 조회는 BoardController에서 get()메서드로 구성되어 있습니다.
views/board 폴더 내 get.jsp를 register.jsp를 복사해서 작성 해줍니다.
get.jsp는 게시물의 번호를 보여줄 수 있는 필드를 추가하고 모든 데이터는 readonly를 지정해서 작성 합니다.
register.jsp있던 <form> 태그는 조회 페이지에서는 사용하지 않으므로 제거하고,
마지막에는 수정/삭제 페이지로 이동하거나 원래의 목록 페이지로 이동할 수 이는 버튼을 추가 합니다.
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>
<%@include file="../includes/header.jsp"%>
<div class="row">
<div class="col-lg-12">
<h1 class="page-header">Board Read</h1>
</div>
<!-- /.col-lg-12 -->
</div>
<!-- /.row -->
<div class="row">
<div class="col-lg-12">
<div class="panel panel-default">
<div class="panel-heading">Board Read Page</div>
<!-- /.panel-heading -->
<div class="panel-body">
<div class="form-group">
<label>Bno</label> <input class="form-control" name='bno'
value='<c:out value="${board.bno }"/>' readonly="readonly">
</div>
<div class="form-group">
<label>Title</label> <input class="form-control" name='title'
value='<c:out value="${board.title }"/>' readonly="readonly">
</div>
<div class="form-group">
<label>Text area</label>
<textarea class="form-control" rows="3" name='content'
readonly="readonly"><c:out value="${board.content}" /></textarea>
</div>
<div class="form-group">
<label>Writer</label> <input class="form-control" name='writer'
value='<c:out value="${board.writer }"/>' readonly="readonly">
</div>
<%-- <button data-oper='modify' class="btn btn-default">
<a href="/board/modify?bno=<c:out value="${board.bno}"/>">Modify</a></button>
<button data-oper='list' class="btn btn-info">
<a href="/board/list">List</a></button> --%>
<button data-oper='modify' class="btn btn-default">Modify</button>
<button data-oper='list' class="btn btn-info">List</button>
<%-- <form id='operForm' action="/boad/modify" method="get">
<input type='hidden' id='bno' name='bno' value='<c:out value="${board.bno}"/>'>
</form> --%>
<form id='operForm' action="/boad/modify" method="get">
<input type='hidden' id='bno' name='bno' value='<c:out value="${board.bno}"/>'>
<input type='hidden' name='pageNum' value='<c:out value="${cri.pageNum}"/>'>
<input type='hidden' name='amount' value='<c:out value="${cri.amount}"/>'>
<input type='hidden' name='keyword' value='<c:out value="${cri.keyword}"/>'>
<input type='hidden' name='type' value='<c:out value="${cri.type}"/>'>
</form>
</div>
<!-- end panel-body -->
</div>
<!-- end panel-body -->
</div>
<!-- end panel -->
</div>
<!-- /.row -->
<script type="text/javascript">
$(document).ready(function() {
var operForm = $("#operForm");
$("button[data-oper='modify']").on("click", function(e){
operForm.attr("action","/board/modify").submit();
});
$("button[data-oper='list']").on("click", function(e){
operForm.find("#bno").remove();
operForm.attr("action","/board/list")
operForm.submit();
});
});
</script>
<%@include file="../includes/footer.jsp"%>
브라우저에서는 /board/get?bno=1과 같이 게시물의 번호를 반드시 파라미터로 전달 해야 합니다.
화면 하단의 버튼은 /board/list와 board/modify?bno=xx와 같이 이동하는 링크를 추가 합니다.
11.4.2 목록 페이지와 뒤로가기 문제
목록 페이지에서 각 게시물 제목에 <a>태그를 적용해서 조회 페이지로 이동하게 처리 합니다.
최근에 웹페이지들은 사용자의 트래픽을 고려해 목록 페이지에서 새 창을 띄워 조회 페이지로 이동하는 방식을 선호하지만, 전통적인 방식에서는 현재창 내에서 이동하는 방식을 사용 합니다.
목록에서 조회 페이지로의 이동
list.jsp는 아래와 같이 조금 수정 합니다.
<td><a href='/board/get?bno=<c:out value="${board.bno}"/>'><c:out value="${board.title}"/></a></td>
<!-- <td><c:out value="${board.title}"/></td> -->
브라우저를 통해 확인해보면 각 게시물의 제목에 링크가 걸려 있습니다.
제목을 클릭하면 조회 페이지로 이동 하는 것을 볼 수 있습니다.
조회 페이지로의 이동은 JavaScript를 이용해서 처리할 수도 있고 위와 같이 <a> 태그를 이용해서도 처리가 가능 합니다.
만일 조회페이지를 이동하는 방식이 아니라 새창을 통해 보고 싶다면 <a> 태그의 속성으로 target=_blank를 지정하면 됩니다.
<a>태그와 <form>태그에는 target 속성을 지정할 수 있는데 _blank는 새로운 창에서 처리 됩니다.
뒤로가기의 문제
동일한 페이지 내에서 목록 페이지와 조회 페이지의 이동은 정상적으로 처리된것 같아 보이지만 한가지 문제가 남아있습니다.
등록 -> 목록 -> 조회 까지는 순조롭지만 브라우저의 뒤로가기를 선택하는 순간 다시 게시물의 등록 결과를 확인하는 방식으로 동작한다는 것입니다.
아래는 문제가 생기는 부분을 그림으로 표현한 것입니다.
이러한 문제가 생기는 원인은 브라우저에서 뒤로가기나 앞으로가기를 하면 서버를 다시 호출하는게 아니라 과거에 자신이 이 가진 데이터를 활용하기 떄문입니다.
브라우저에서 조회 페이지와 목록 페이지를 여러 번 앞으로 혹은 뒤로 이동해도 서버에서는 처음에 호출을 제외하고는 별다른 변화가 없는 것을 확인할 수 있습니다.
이 문제를 해결하려면 windows의 history 객체를 이용해서 현재 페이지에는 모달창을 띄울 필요가 없다고 표시를 해 두면 됩니다.
windows의 history 객체는 스텍 구조로 동작하기 때문에 아래와 같습니다.
그림 1은 사용자가 브라우저를 열고 /board/list를 최초로 호출한 것입니다.
history에 쌒으면서 현재 페이지는 모달창을 보여줄 필요가 없다는 표시(네모칸) 을 해둡니다.
그림 2는 사용자가 /board/register를 호출한 경우 입니다.
스택 상단에 /board/register가 쌓이게 됩니다.
만일 이 상태에서 뒤로가기를 한다면 미리 심어둔 표시를 이용해 모달창을 띄울 필요가 없다는 것을 확인할 수 있습니다.
windows.history객체를 조작하는 것은 이론적으로는 복잡해 보이지만 코드는 아래와 같이 간단합니다.
history.replaceState({}, null, null);
function checkModal(result) {
if (result === '' ) {
return;
}
if (parseInt(result) > 0 || history.state) {
$(".modal-body").html("게시글 " + parseInt(result) + " 번이 등록되었습니다.");
}
기존과 달라진점은 맨 마지막에 추가된 history.replaceState부분과 checkModal()에서 history.state를 체크하는 부분입니다.
JavaScript의 처리는 우선 checkModal()을 실행하는데, 만일 등록된 후에 이동된 것이라면 그림 3처럼 되기 때문에 모달창이 보이게 됩니다.
모달창이 보이는 여부와 관계없이 JavaScript의 모든 처리가 끝나게 되면 history에 쌓이는 상태는 모달창을 보여줄 필요가 없는 상태가 됩니다.
11.5 게시물의 수정/삭제 처리
게시물의 수정 작업은 일반적으로 아래 방식으로 처리 합니다.
- 조회 페이지에서 직접 처리하는 방식
- 별도의 수정/삭제 페이지를 만들어서 해당 페이지에서 수정과 삭제를 처리하는 방식
최근에는 게시물의 조회 페이지에서 댓글 등에 대한 처리가 많아지면서 수정과 삭제는 별개의 페이지에서 처리 합니다.
조회 페이지에서는 GET 방식으로 처리되는 URL을 통해서 수정/삭제 버튼이 존재하는 화면을 볼 수 있게 제작 해야 합니다.
수정 / 삭제 작업은 POST 방식으로 처리되고, 결과는 다시 목록화면에서 확인할수 있는 형태로 제작 합니다.
11.5.1수정/삭제 페이지로 이동
BoardController에서 수정/삭제가 가능한 화면으로 이동하는 것은 조회 페이지와 같습니다.
따라서 기존의 get() 메소드를 조금 수정해서 화면을 구성 합니다.
@GetMapping이나 @PostMapping은 URL을 배열로 처리할 수 있으므로 위와 같이 하나의 메서드로 여러 URL을 처리할 수 있습니다.
브라우저에서는 /board/modify?bno=30과 같은 방식으로 처리하므로 views 폴더 내에 modify.jsp를 작성 합니다.
modify.jsp는 get.jsp와 같지만 수정이 가능한 제목이나 내용 등이 readonly속성이 없도록 작성 합니다.
POST방식으로 처리하는 부분은 <form>태그로 내용들을 감싸게 합니다.
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>
<%@include file="../includes/header.jsp"%>
<div class="row">
<div class="col-lg-12">
<h1 class="page-header">Board Modify</h1>
</div>
<!-- /.col-lg-12 -->
</div>
<!-- /.row -->
<div class="row">
<div class="col-lg-12">
<div class="panel panel-default">
<div class="panel-heading">Board Modify</div>
<!-- /.panel-heading -->
<div class="panel-body">
<form role="form" action="/board/modify" method="post">
<input type='hidden' name='pageNum' value='<c:out value="${cri.pageNum }"/>'>
<input type='hidden' name='amount' value='<c:out value="${cri.amount }"/>'>
<input type='hidden' name='type' value='<c:out value="${cri.type }"/>'>
<input type='hidden' name='keyword' value='<c:out value="${cri.keyword }"/>'>
<div class="form-group">
<label>Bno</label>
<input class="form-control" name='bno'
value='<c:out value="${board.bno }"/>' readonly="readonly">
</div>
<div class="form-group">
<label>Title</label>
<input class="form-control" name='title'
value='<c:out value="${board.title }"/>' >
</div>
<div class="form-group">
<label>Text area</label>
<textarea class="form-control" rows="3" name='content' ><c:out value="${board.content}"/></textarea>
</div>
<div class="form-group">
<label>Writer</label>
<input class="form-control" name='writer'
value='<c:out value="${board.writer}"/>' readonly="readonly">
</div>
<div class="form-group">
<label>RegDate</label>
<input class="form-control" name='regDate'
value='<fmt:formatDate pattern = "yyyy/MM/dd" value = "${board.regdate}" />' readonly="readonly">
</div>
<div class="form-group">
<label>Update Date</label>
<input class="form-control" name='updateDate'
value='<fmt:formatDate pattern = "yyyy/MM/dd" value = "${board.updateDate}" />' readonly="readonly">
</div>
<button type="submit" data-oper='modify' class="btn btn-default">Modify</button>
<button type="submit" data-oper='remove' class="btn btn-danger">Remove</button>
<button type="submit" data-oper='list' class="btn btn-info">List</button>
</form>
</div>
<!-- end panel-body -->
</div>
<!-- end panel-body -->
</div>
<!-- end panel -->
</div>
<!-- /.row -->
<script type="text/javascript">
$(document).ready(function() {
var formObj = $("form");
$('button').on("click", function(e){
e.preventDefault();
var operation = $(this).data("oper");
console.log(operation);
if(operation === 'remove'){
formObj.attr("action", "/board/remove");
}else if(operation === 'list'){
//move to list
formObj.attr("action", "/board/list").attr("method","get");
var pageNumTag = $("input[name='pageNum']").clone();
var amountTag = $("input[name='amount']").clone();
var keywordTag = $("input[name='keyword']").clone();
var typeTag = $("input[name='type']").clone();
formObj.empty();
formObj.append(pageNumTag);
formObj.append(amountTag);
formObj.append(keywordTag);
formObj.append(typeTag);
}
formObj.submit();
});
});
</script>
<%@include file="../includes/footer.jsp"%>
<form> 태그는 action 속성을 /board/modify로 지정했지만,
삭제를 하면 /board/remove와 같이 action 속성의 내용을 수정해서 사용하게 됩니다.
게시물의 제목,내용은 수정이 가능한 형태로 사용자가 편집이 가능 하지만
등록일,수정일은 나중에 BoardVO로 수집되어야 하므로 yyyy/mm/dd의 포맷으로 해야 합니다.
(만일 포맷이 맞지 않으면 파라미터 수집 부분에 문제가 생기므로 주의해야 합니다.)
마지막에는 수정/삭제/목록 버튼을 추가 합니다.
브라우저에서는 http://localhost:8080/board/modify?bno=1이 정상적으로 호출되는지 확인 합니다.
이때 bno는 게시물이 있는 번호를 넣어주면 됩니다.
(regdate와 updatedate는 hidden처리된 상태라 화면에는 보여지지 않습니다)
JavaScript에서는 위 버튼에 따라 다른 동작을 할 수 있게 수정 합니다.
<script type="text/javascript">
$(document).ready(function() {
var formObj = $("form");
$('button').on("click", function(e){
e.preventDefault();
var operation = $(this).data("oper");
console.log(operation);
if(operation === 'remove'){
formObj.attr("action", "/board/remove");
}else if(operation === 'list'){
//move to list
formObj.attr("action", "/board/list").attr("method","get");
var pageNumTag = $("input[name='pageNum']").clone();
var amountTag = $("input[name='amount']").clone();
var keywordTag = $("input[name='keyword']").clone();
var typeTag = $("input[name='type']").clone();
formObj.empty();
formObj.append(pageNumTag);
formObj.append(amountTag);
formObj.append(keywordTag);
formObj.append(typeTag);
}
formObj.submit();
});
});
</script>
JavaScript에서는 <button> 태그의 data-oper 속성을 이용해서 원하는 기능을 동작하도록 처리 합니다.
<form>태그의 모든 버튼은 기본적으로 submit으로 처리하기 때문에 e.preventDefault()로 기본 동작을 막고 마지막에 직접 submit()을 수행합니다.
11.5.2 게시물 수정/삭제 확인
화면에서 게시물을 수정한 후에 modify 버튼을 통해서 BoardController에 수정을 요청합니다.
Modify 버튼을 클릭하면 BOardController에서는 주어진 파라미터들을 BoardVO로 처리하게 되고, 다음과 같이 수정된 값이 제대로 수집된 것을 볼 수 있습니다.
수정된 후에는 /board/list화면으로 이동하게 됩니다.
이 경우에 해나 처리는 이미 완료 되었으므로 모달창을 통해 메세지를 확인할 수 있습니다.
화면에서 Remove 버튼을 클릭하게되면 <form> 태그의 action 값이 /board/remove가 되고 데이터가 전송 됩니다.
물론 BoardController에서는 bno값 하나만 필요하지만 처리에는 문제가 없습니다.
삭제 시 BoardController에는 아래와 같은 로그가 기록되게 됩니다.
삭제도 목록페이지로 이동되게 됩니다.
11.5.3 조회 페이지에서 <form>처리
게시물의 조회페이지에서는 수정과 삭제가 필요한 페이지로 링크를 처리해야 합니다.
직접 버튼에 링크를 처리하는 방식을 사용하여 처리 하였지만 나중에 다양한 상황을 처리하기 위해 <form> 태그를 이용해서 수정해봅니다.
브라우져에서는 <form>태그의 내용은 보이지 않고 버튼만 보이게 됩니다.
사용자가 버튼을 클릭하게되면 openForm이라는 id를 가진 <form>태그를 전송해야 하므로 JavaScript의 처리가 필요합니다.
사용자가 수정 버튼을 누르는 경우에는 bno값을 같이 전달하고 <form> 태그를 submit 시켜 처리 합니다.
만일 사용자가 list로 이동하는 경우에는 아직 아무런 데이터도 필요하지 않으므로 <form> 태그 내의 bno 태그를 지우고 submit을 통해서 리스트 페이지로 이동 합니다.
11.5.4 수정 페이지에서 링크 처리
수정 페이지에서는 사용자가 다시 목록 페이지로 이동할 수 있도록 하기 위해 JavaScript를 수정해보겠습니다.
클릭한 버튼이 List인경우 action 속성과 method 속성을 변경합니다.
/board/list로의 이동은 아무런 파라미터가 없기 때문에 <form> 태그의 모든 내용은 삭제한 상태에서 submit()을 진행 합니다.
이후 코드는 실행되지 않도록 return을 통해서 제어 합니다.
이 장에서 진행된 모든 내용을 도식화하면 아래 구조가 됩니다.
'개발관련 > 코드로 배우는 스프링 웹 프로젝트(개정판)' 카테고리의 다른 글
Part3 - MyBatis와 스프링에서 페이징 처리 Chapter13 (0) | 2023.06.04 |
---|---|
Part3 - 오라클 데이터베이스 페이징 처리 Chapter12 (0) | 2023.06.01 |
Part3 - 프레젠테이션(웹) 계층의 CRUD 구현 Chapter10 (0) | 2023.03.19 |
Part3 - 비즈니스 계층 Chapter09 (0) | 2023.03.19 |
Part3 - 영속/비즈니스 계층의 CRUD 구현 Chapter08 (0) | 2023.03.09 |