티스토리 뷰
화면을 개발하기 전에는 반드시 화면의 전체 레이아웃이나 디자인이 반영된 상태에서 개발하는 것을 추천한다.
일부 개발자들은 화면을 나중에 처리한다고 생각하고 진행하는 경우가 있는데 결과적으로는 두 배의 시간을 들이는 결과가 될 가능성이 높기 때문에 권장하지는 않는다.
목록 페이지 작업과 includes
스프링 MVC의 JSP를 처리하는 설정은 Java기준으로
ServletConfig.class
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
InternalResourceViewResolver bean = new InternalResourceViewResolver();
bean.setViewClass(JstlView.class);
bean.setPrefix("/WEB-INF/views/");
bean.setSuffix(".jsp");
registry.viewResolver(bean);
}
스프링 MVC의 설정에서 화면 설정은 ViewResolver라는 객체를 통해서 이루어지는데,
위의 설정을 보면 /WEB-INF/views 폴더를 이용하는 것을 볼 수 있다.
/WEB-INF 경로는 브라우저에서 직접 접근할 수 없는 경로이므로 반드시 Controller를 이용하는 방식을 기본적으로 사용한다.
게시물 리스트의 URL은 /board/list 이므로 최종적인 /WEB-INF/views/board/list.jsp가 된다.해당 경로에 list.jsp 파일을 추가한다.
list.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>List Page</h1>
</body>
</html>
list.jsp는 우선 정상적으로 URL 처리가 되는지를 확인해야 하므로 Tomcat으로 실행해서 확인한다.
Intellij같은 경우에는 내장 Tomcat을 사용하려면 eclipse와 다른 방식으로 등록해준다.


사진처럼 내장 Tomcat을 등록해서 사용하면 된다.
Intellij의 경우에는 경로가 다르기때문에
http://localhost:8080/jex02_war_exploded/board/list
'jex02_war_exploded' 부분 경우엔 Intellij가 자동으로 부여해주기 때문에 따로 신경쓰지 않아도 된다.
주의!
경로에 대한 설정은 css,js,이미지 파일들의 경로에 치명적인 영향을 주기 때문에 처음부터 절대 경로 혹은 상태 경로에 대해서 명확히 결정한 후에 프로젝트를 진행하도록 한다.일반적인 경우라면 절대 경로를 이용하는 것이 좋다.
SB Admin2 페이지 적용하기
정상적으로 /board/list 페이지가 동작한다면 SB Admin2의 pages 폴더에 있는 tables.html 의 내용을 list.jsp의 내용으로 그대로 복사해서 수정하고 실행한다.
수정할 때는 list.jsp의 상단에 JSP의 Page 지시자는 지우지 않아야 한다.
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!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">
<meta name="description" content="">
<meta name="author" content="">
브라우저에서는 css 등이 완전히 깨진 상태이므로 텍스트만 출력되는 것을 볼 수 있다.
css와 js 파일들의 경로를 수정하는 작업은 브라우저의 개발자 도구를 통해서 확인하며 진행한다.
개발자 도구를 통해서 현재 브라우저의 network 부분을 확인하고 페이지를 새로고침하면 잘못된 url의 정보를 확인 할 수 있다.
SB Admin2의 css의 경로는
<!-- Bootstrap Core CSS -->
<link href="../vendor/bootstrap/css/bootstrap.min.css" rel="stylesheet">
인데 현재 프로젝트에서는 제대로 서비스될 수 없다.
ServletConfig.class
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").
addResourceLocations("/resources/");
}
해당 클래스의 부분을 보면 정적인(static) 리소스는 resources 라는 경로를 지정하고 있다.
SB Admin2의 압출을 풀어둔 모든 폴더를 프로젝트 내 webapp 밑의 resources 폴더로 복사해 넣는다.
src/main/webapp/resources
해당 경로로 복사해준다.
파일들을 resources 경로로 넣어도 아직은 페이지에서 경로를 수정하지 않았기 때문에 문제가 생기문 것은 동일하다.
list.jsp 파일에서 css나 js 파일의 경로를 /resources로 시작하도록 수정한다.
(Intellij 같은 경우에는 경로가 조금 다를 수 있으니 해당 경로로 확인하면 된다.)
<!-- Bootstrap Core CSS -->
<link href="../vendor/bootstrap/css/bootstrap.min.css" rel="stylesheet">
.
.
.
\/
<!-- Bootstrap Core CSS -->
<link href="../resources/vendor/bootstrap/css/bootstrap.min.css" rel="stylesheet">
css,js 파일들의 링크는 모든 페이지에서 사용될 것이므로 화면에서 디자인이 깨지지 않는 것을 확인한 후 다음 내용을 진행한다.
includes 적용
jsp를 작성할 때마다 많은 양의 html 코드를 이용하는 것을 피하기 위해 jsp의 includes 지시자를 활용해서 페이지 제작시에 필요한 내용만을 작성할 수 있게 사전에 작업을 해야 한다.
현재 프로젝트 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의 내용으로 처리한다.
footer.jsp 적용
<div id='page-wrapper'>가 끝나는 태그부터 마지막까지는 footer.jsp의 내용으로 작성한다.
최종적으로 수정하였다면 브라우저를 통해 정상적으로 동작하는지 테스트해보는것이 좋다.
jQuery 라이브러리 변경
jsp 페이지를 작성하다 보면 js로 브라우저 내에서의 조작이 필요한 경우가 많다.
예제는 jQuery를 이용할 것인데 문제는 위의 방식대로 처리했을 때 jQuery 라이브러리가 footer.jsp 내에 포함되어 있다는 점이다.성능을 조금 손해 보더라도 jQuery를 header.jsp에 선언해 두면 작성하는 jsp에서 자유롭게 사용할 수 있으므로 수정해야 한다.
footer.jsp의 상단에 있는 jquery.min.js 파일의 <script> 태그를 제거한다.
jQuery는 인터넷을 통해서 다운로드 받을 수 있게 jQuery의 링크를 검색해서 header.jsp 내에 추가해야 한다.
https://developers.google.com/speed/libraries
Hosted Libraries | Google Developers
A stable, reliable, high-speed, globally available content distribution network for the most popular open-source JavaScript libraries.
developers.google.com

header.jsp
...
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<link href="../resources/vendor/bootstrap/css/bootstrap.min.css" rel="stylesheet">
<!-- Bootstrap Core JavaScript -->
<script src="../resources/vendor/bootstrap/js/bootstrap.min.js"></script>
<!-- Metis Menu Plugin JavaScript -->
<script src="../resources/vendor/metisMenu/metisMenu.min.js"></script>
...
반응형 웹 처리
SB Admin2는 반응형으로 설계되어 있어서 브라우저의 크기에 맞게 모바일용으로 자동으로 변경되지만 jQuery의 최신 버전을 사용한 상태에서는 모바일 크기에서 새로고침시 메뉴가 펼쳐지는 문제가 발생한다.
이 문제를 해결하기 위해서 includes 폴더 내 footer.jsp에 아래와 같은 코드를 기존 코드 대신에 추가한다.
<script>
$(document).ready(function() {
...
$(".sidebar-nav")
.attr("class", "sidebar-nav navbar-collapse collapse")
.attr("aria-expanded", "false")
.attr("style", "height:1px");
});
</script>
목록 화면 처리
list.jsp 페이지의 일부를 include 하는 방식으로 처리했음에도 많은 html의 내용들이 존재하므로 아래와 같이 최소한의 태그들만 적용시킨다.
list.jsp에는 JSTL의 출력과 포멧을 적용할 수 있는 태그 라이브러리를 추가한다.
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<!DOCTYPE html>
<%@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 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>
</div>
</div>
<!-- /.row -->
<%@include file="../includes/footer.jsp"%>
수정된 list.jsp를 저장하고 브라우저를 통해서 원하는 형태로 출력되는지 확인한다.
Model에 담긴 데이터 출력
/board/list를 실행했을 때 이미 BoardController는 Model을 이용해서 게시물의 목록을 list라는 이름으로 담아서 전달했으므로 list.jsp에서는 이를 출력한다.출력은 JSTL을 이용해서 처리한다.
list.jsp내에 <tbody> 태그와 각<tr>을 아래와 같이 입력한다.
...
<table 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>
...
브라우저를 통해서 결과를 확인하면 데이터베이스에 있는 전체 목록이 출력된다.

등록 입력 페이지와 등록 처리
게시물의 등록 작업은 POST 방식으로 처리하지만,화면에서 입력을 받아야 하므로 GET 방식으로 입력 페이지를 볼 수 있도록 BoardController에 메서드를 추가한다.
BoardController.class
@GetMapping("/register")
public void register() {
}
register()는 입력 페이지를 보여주는 역할만을 하기 때문에 별도의 처리가 필요하지 않는다.
views 폴더에는 includes를 적용한 입력 페이지를 작성한다.
register.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<!DOCTYPE html>
<%@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>
</div>
</div>
</div>
<%@include file="../includes/footer.jsp" %>
register.jsp 페이지에서는 <form> 태그를 이용해서 필요한 데이터를 전송한다.
<input>이나 <textarea> 태그의 name 속성은 BoardVO 클래스의 변수와 일치시켜 준다.
브라우저를 통해 /board/register 화면이 제대로 출력되는지 확인한다.

화면이 정삭적으로 보인다면 입력 항목을 넣어서 새로운 게시물이 등록되는지를 확인한다.
BoardController의 POST 방식으로 동작하는 register()는 redirect 시키는 방식을 이용하므로 게시물의 등록 후에는 다시 /board/list 로 이동하게 된다.
간혹 게시물의 등록은 정상적으로 이루어지지만 한글이 깨지는 문제가 발생할 때가 있다.
한글 문제와 UTF-8 필터 처리
새로운 게시물을 등록했을 때 만일 한글 입력에 문제가 있다는 것을 발견했다면
1.브라우저에서 한글이 깨져서 전송되는지를 확인하고
2.문제가 없다면 스프링 MVC 쪽에서 한글을 처리하는 필터를 등록해야 한다.
브라우저에서 전송되는 데이터는 개발자 도구를 이용해서 확인할 수 있다.
개발자 도구에서 Network 탭을 열어둔 상태에서 데이터를 보내면 해당 내용을 볼 수 있으므로
이때 POST 방식으로 제대로 전송되었는지 한글이 깨진 상태로 전송된 것인지를 확인 할 수 있다.

현재 나온 화면에서는 문제가 없지만
문자가 깨져서 나오는것을 보면 브라우저가 한글을 문제없이 보냈음을 알 수 있는데
문제는 Controller 혹은 데이터베이스 쪽이라는 것을 알 수 있다.
BoardController와 BoardServiceImpl을 개발할 때는 이미 Lombo의 로그를 이용해서 필요한 기능들을 기록해 두었으므로 이를 확인해야 한다.
...
INFO : com.osk2090.controller.BoardController - list
INFO : com.osk2090.controller.BoardController - register: BoardVO(bno=null, title=asda, content=sdasd, writer=asda, regdate=null, updatedate=null)
INFO : com.osk2090.controller.BoardController - list
...
현재 필자의 로그에는 문자가 깨지지 않았지만 문자가 깨졌다면 데이터를 전송하는 과정에서 깨졌다는 것을 알수 있을 것이다.
만약 그 문제가 발생하였다면 Java의 경우 WebConfig.class 에 메서드를 재정의 해준다.
@Override
protected Filter[] getServletFilters() {
CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
characterEncodingFilter.setEncoding("UTF-8");
characterEncodingFilter.setForceEncoding(true);
return new Filter[]{characterEncodingFilter};
}
한글에 대한 처리가 끝난 후 다시 게시물을 작성해 보면 한글에 문제가 없이 데이터가 전송되는 것을 알 수 있을 것이다.
재전송(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>
만일 새로운 게시물이 등록된 직후에 위의 코드는 다음과 같이 처리된다.
<script type="text/javascript">
$(document).ready(function (){
var result = 처리된 번호 출력;
})
</script>
새로운 게시물의 번호는 addFlashAttribute()로 저장되었기 때문에 한 번도 사용된 적이 없다면
위와 같이 값을 만들어 내지만 사용자가 /board/list를 호출하거나, 새로고침을 통해서 호출하는 경우는 아래와 같이 아무런 내용이 없게 된다.
<script type="text/javascript">
$(document).ready(function (){
var result = '';
})
</script>
addFlashAttribute()를 이용해서 일회성으로만 데이터를 사용할 수 있으므로 이를 이용해서 경고창이나 모달창 등을 보여주는 방식으로 처리할 수 있다.
모달(Modal)창 보여주기
최근에는 브라우저에서 경고창을 띄우는 방식보다 모달창을 보여주는 방식을 많이 사용한다.
BootStrap은 모달창을 간단하게 사용할 수 있으므로 목록 화면에서 필요한 메시지를 보여주는 방법을 사용해 보자.
모달창은 기본적으로 <div>를 화면에 특정 위치에 보여주고,배경이 되는 <div>에 배경색을 입혀서 처리한다.
모달창은 활성화된 <div>를 선택하지 않고는 다시 원래의 화면을 볼 수 없도록 막기 때문에 메시지를 보여주는데 효과적인 방식이다.모달창에 대한 코드는 다운로드한 SBAdmin2의 pages 폴더 내 notifications.html 파일을 참고하면 된다.
모달창을 처리하기 위해서는 우선 <div>를 이용해서 페이지의 코드에 추가해야 한다.
list.jsp내에 <table>태그의 아래쪽에 모달창의 <div>를 추가한다.
<script type="text/javascript">
$(document).ready(function (){
var result = '<c:out value="$(result}"/> ';
checkModal(result);
function checkModal(result){
if (result === '') {
return;
}
if (parseInt(result) > 0) {
$(".modal-body").html("게시글 " + parseInt(result) + " 번이 등록되었습니다.");
}
$("#myModal").modal("show");
}
})
</script>
checkModal() 함수는 파라미터에 따라서 모달창을 보여주거나 내용을 수정한 뒤 보이도록 작성한다.
checkModal()에소는 새로운 게시글이 작성되는 경우 RedirectAttibutes로 게시물의 번호가 전송되므로 이를 이용해서 모달 창의 내용을 수정한다.$("#modal").modal)"show")를 호출하면 위의 화면처러 모달창이 보이게 된다.
이제 /board/register를 이용해서 새로운 게시물을 작성하고 나면 자동으로 /board/list로 이동하면서 모달창이 보이게 된다.
목록에서 버튼으로 이동하기
게시물의 작성과 목록 페이지로 이동이 정상적으로 동작했다면 마지막으로 목록 페이지 상단에 버튼을 추가해서 작업을 시작할 수 있게 처리하는 것이다.
list.jsp
...
<div class="panel-heading">
Board List Page
<button id="regBtn" type="button" class="btn btn-xs pull-right">Register New Board</button>
</div>
...
<script type="text/javascript">
$(document).ready(
function (){
var result = '<c:out value="${result}"/>';
checkModal(result);
function checkModal(result) {
if (result === '') {
return;
}
if (parseInt(result) > 0) {
$(".modal-body").html(
"게시글 " + parseInt(result) + " 번이 등록되었습니다.");
}
$("#myModal").modal("show");
}
$("#regBtn").on("click", function () {
self.location = "../board/register";
});
}
);
</script>
이제 따로 URL로 타이핑하지 않고 게시물 등록을 할 수 있게 되었다.
조회 페이지와 이동
게시물의 등록과 리스트 처리가 끝났다면 가장 중요한 틀은 완성되었다고 볼 수 있다.
다름으로는 목록 페이지에서 링크를 통해 GET 방식으로 특정한 번호의 게시물을 조회할 수 있는 기능을 작성한다.
조회 페이지 작성
조회 페이지는 입력 페이지와 거의 유사하지만 게시물 번호(bno)가 출력된다는 점과 모든 데이터가 읽기 전용으로 처리된다는 점이 가장 큰 차이이다.
게시물의 조회는 BoardController에서 get() 메서드로 구성되어 있다.
BoardController.class
@GetMapping("/get")
public void get(@RequestParam("bno") Long bno, Model model) {
log.info("/get");
model.addAttribute("board", service.get(bno));
}
views/board 폴더 내 get.jsp를 register.jsp를 복사해서 작성한다.
get.jsp는 게시물 번호를 보여줄 수 있는 필드를 추가하고, 모든 데이터는 readonly를 지정해서 작성한다.
register.jsp에 있던 <form> 태그는 조회 페이지에서는 그다지 필요하지 않으므로 제거하는 대신 마지막에는 수정/삭제 페이지로 이동하거나 원래의 목록 페이지로 이동할 수 있는 버튼을 추가한다.
get.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<!DOCTYPE html>
<%@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">Modify</button>
<button data-oper="list" class="btn btn-info">List</button>
</div>
</div>
</div>
</div>
<%@include file="../includes/footer.jsp" %>
브라우저에서 /board/get?bno=숫자 와 같이 게시물의 번호를 반드시 파라미터로 전달해야 한다.
파라미터로 전달하는 bno 값이 존재한다면 아래와같이 출력될 것이다.

화면 하단의 버튼은 /board/list와 /board/modify?bno=xx 와 같이 이동하는 링크를 추가한다.
<button data-oper="modify" class="btn btn-default"
onclick="location.href='../board/modify?bno=<c:out value="${board.bno}"/>'">
Modify
</button>
<button data-oper="list"
class="btn btn-info"
onclick="location.href='../board/list'">List</button>
목록 페이지와 뒤로 가기 문제
목록 페이지에서 각 게시물 제목에 <a>태그를 적용해서 조회 페이지로 이동하게 처리한다.
최근에 웹페이지들은 사용자들의 트래픽을 고려해 목록 페이지에서 새창을 띄워서 조회 페이지로 이동하는 방식을 선호하지만 전통적인 방식에서는 현재창 내에서 이동하는 방식을 사용한다.
의외로 이러한 처리가 제대로 되지 않는 경우들을 많이 보게 된다.
목록에서 조회 페이지로의 이동
list.jsp 페이지는 다음과 같이 수정한다.
<c:forEach items="${list}" var="board">
<tr>
<td><c:out value="${board.bno}"/></td>
<td><a href='../board/get?bno=<c:out value="${board.bno}"/>'>
<c:out value="${board.title}"/><a/></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>
브라우저를 통해서 화면을 확인해 보면 각 게시물의 제목에 링크가 걸리는 것을 확인할 수 있고,
제목을 클릭하면 정상적으로 조회 페이지로 이동하는 것을 볼 수 있다.
조회 페이지로 이동은 JavaScript를 이용해서 처리할 수 있고 위와 같이 <a> 태그를 이용해서도 처리가 가능하다.
만일 조회 페이지를 이동하는 방식이 아니라 새창을 통해서 보고 싶다면
<a> 태그의 속성으로 target='_blank'를 지정하면 된다.<a>태그와 <form> 태그에는 target 속성을 지정할 수 있는데
'_blank'는 새로운 창에서 처리된다.
<td><a target="_blank" href='../board/get?bno=<c:out value="${board.bno}"/>'>
<c:out value="${board.title}"/><a/></td>
아래의 사진은 목록에서 target='_blank'를 지정했을 때 새로운 창이 옆에 뜨는 모습이다.

뒤로가기의 문제
동일한 페이지 내에서 목록 페이지와 조회 페이지의 이동은 정상적으로 처리된 것 같아 보이지만
한 가지 문제가 남아 있다.등록->목록->조회 까지 순조롭지만 브라우저의 뒤로가기를 선택하는 순간 다시 게시물의 등록 결과를 확인하는 방식으로 동작한다는 것이다.
이러한 문제가 생기는 원인은 브라우저에서 뒤로가기나 앞으로 가기를 하면 서버를 다시 호출하는 것이 아니라 과거에 자신이 가진 데이터를 활용하기 때문이다.
브라우저에서 조회 페이지와 목록 페이지를 여러 번 앞으로 혹은 뒤로 이동해도 서버에서는 처음에 호출을 제외하고 별다른 변화가 없는 것을 확인할 수 있다.
이 문제를 해결하려면 window의 history 객체를 이용해서 현재 페이지는 모달창을 뜨울 필요가 없다고 표시를 해 두는 방식을 이용해야 한다.
window의 history 객체는 스택 구조로 동작한다.

그림1은 사용자가 브라우저를 열고 /board/list를 최초로 호출한 것이다.
history에 쌓으면서 현재 페이지는 모달창을 보여줄 필요가 없다는 표시(그림의 작은상자)를 해둔다.
그림2는 사용자가 /board/register를 호출한 경우이다.스택의 상단에 /board/register가 쌓이게 된다.
만일 이 상태에서 뒤로가기를 실행하면 아래쪽의 /board/list가 보여지는데 이때 심어둔 표시를 이용해서 모달창을 띄울 필요가 없다는 것을 확인할 수 있다.
그림3은 사용자가 등록을 완료하고 /board/list가 호출되는 상황이다.
브라우저에서 앞으로가기나 뒤로가기로 이동한 것이 아니므로 스택의 상단에 추가된다.등록 직후에 /board/list로 이동하는 경우에는 모달창이 동작한다.
그림3에서 모달창을 보여준 후에는 스택의 상단에 모달창이 필요하지 않다는 표시를 해주어야 한다.
이후에 /board/register를 호출하면 그림 4와 같이 된다.
window.history 객체를 조작하는 것은 이론으로는 복잡해 보이지만 사실 코드는 약간의 변화가 있을 뿐이다.
list.jsp
<script type="text/javascript">
$(document).ready(
function () {
var result = '<c:out value="${result}"/>';
checkModal(result);
history.replaceState({}, null, null);
function checkModal(result) {
if (result === '' || history) {
return;
}
if (parseInt(result) > 0) {
$(".modal-body").html(
"게시글 " + parseInt(result) + " 번이 등록되었습니다.");
}
$("#myModal").modal("show");
}
$("#regBtn").on("click", function () {
self.location = "../board/register";
});
});
</script>
기존과 달라진 점은 맨 마지막에 추가된 history.replaceState() 부분과 checkModal() 에서 history.state를 체크하는 부분이다.
javaScript의 처리는 우선 checkModal()을 실행하는데 만일 등록된 후에 이동한 것이라면 위의 그림3처럼 되기때문에 모달창이 보이게 된다.
모달창이 보이는 여부와 관계없이 javaScript의 모든 처리가 끝나게 되면 history에 쌓이는 상태는 모달창을 보여줄 필요가 없는 상태가 된다.최종적인 결과는 아래과 같이 처리된다.

게시물의 수정/삭제 처리
게시물의 수정 작업은 일반적으로
1.조회 페이지에서 직접 처리하는 방식
2.별도의 수정/삭제 페이지를 만들어서 해당 페이지에서 수정과 삭제를 처리하는 방식을 많이 사용한다.
최근에는 게시물의 조회 페이지에서 댓글 등에 대한 처리가 많아지면서 수정과 삭제는 별개의 페이지에서 하는것이 일반적이다.
조회 페이지에서는 GET 방식으로 처리되는 URL을 통해서 수정/삭제 버튼이 존재하는 화면을 볼 수 있게 제작해야 한다.
수정 혹은 삭제 작업은 POST 방식으로 처리되고 결과는 다시 목록 화면에서 확인할 수 있어야 한다.
수정/삭제 페이지로 이동
BoardController에서 수정/삭제가 가능한 화면으로 이동하는 것은 조회 페이지와 같다.따라서 get()메서드를 조금 수정해서 화면을 구성한다.
BoardController.class
@GetMapping({"/get", "/modify"})
public void get(@RequestParam("bno") Long bno, Model model) {
log.info("/get or modify");
model.addAttribute("board", service.get(bno));
}
@GetMapping이나 @PostMapping 등에는 URL을 배열로 처리할 수 있으므로
위와 같이 하나의 메서드로 여러 URL을 처리할 수 있다.
브라우저에서는 /board/modify?bno=숫자 와 같은 방식으로 처리하므로 views폴더 내 modify.jsp를 작성한다.
modify.jsp는 get.jsp와 같지만 수정이 가능한 제목이나 내용 등이 readonly 속성이 없도록 작성한다.
POST 방식으로 처리하는 부분을 위해서는 <form> 태그로 내용들을 감싸게 한다.
modify.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
<!DOCTYPE html>
<%@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 Page</div>
<!-- /.panel-heading -->
<div class="panel-body">
<form role="form" action="../board/modify" method="post">
<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>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>
</div>
</div>
</div>
<%@include file="../includes/footer.jsp" %>
<form>태그는 action 속성을 /board/modify로 지정했지만 삭제를 하면 /board/remove 와 같이 action 속성의 내용을 수정해서 사용하게 된다.게시물의 제목,내용은 수정이 가능한 형태로 사용해서 사용자가 편집할 수 있게 된다.
등록일과 수정일은 나중에 BoardVO로 수집되어야 하므로 날짜 포멧을 yyyy/MM/dd의 포멧으로 해야 한다.
브라우저에서는 http://localhost:8080/board/mpdify?bno='존재하는 번호'와 같이 게시물 번호를 이용해서 수정 페이지가 정상적으로 출력되는지 확인한다.

javascript에서는 버튼에 따라서 다른 동작을 할 수 있도록 한다.
modify.jsp
<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') {
self.location = "../board/list";
return;
}
formObj.submit();
});
});
</script>
javascript에서는 <button> 태그에 data-oper 속성을 이용해서 원하는 기능을 동작하도록 처리한다.
<form>태그의 모든 버튼은 기본적으로 submit으로 처리하기 때문에 e.preventDefault()로 기본 동작을 막고 마지막에 직접 submit()을 수행한다.
게시물 수정/삭제 확인
화면에서 게시물을 수정한 후에 modify 버튼을 통해서 BoardController에 수정을 요청한다.
Modify 버튼을 클릭하면 BoardController에서는 주어진 파라미터들을 BoardVO로 처리하게 되고 다음과 같이 수정된 값이 제대로 수집되는 것을 볼 수 있다.
...
INFO : com.osk2090.controller.BoardController - /get or modify
INFO : com.osk2090.controller.BoardController - modify:BoardVO(bno=24, title=ㄴㅇㅁㄴ, content=ㅁㄴㅇㅁㄴㅇ, writer=newbie, regdate=Tue Jul 06 00:00:00 KST 2021, updatedate=Tue Jul 06 00:00:00 KST 2021)
INFO : com.osk2090.controller.BoardController - list
...
게시물이 수정된 후에는 다시 /board/list 화면으로 이동하게 된다.그 이후에는 모달창이 뜨게 된다.
화면에서 Remove버튼을 클릭하게 되면 <form> 태그의 action 값이 /board/remove가 되고 데이터들이 전송된다.
물론 BoardController에서는 bno 값 하나만 필요하지만 처리에는 문제가 없다.
삭제 시 BoardController에는 아래와 같은 로그가 출력된다.
INFO : com.osk2090.controller.BoardController - /get or modify
INFO : com.osk2090.controller.BoardController - remove...66
INFO : com.osk2090.controller.BoardController - list
조회 페이지에서 <form> 처리
게시물의 조회 페이지에서 수정과 삭제가 필요한 페이지로 링크를 처리해야 한다.
직접 버튼에 링크를 처리하는 방식을 사용하여 처리하였지만,나중에 다양한 상황을 처리하기 위해서 <form> 태그를 이용해서 수정해보자.
get.jsp
...
<form id="operForm" action="../board/modify" method="get">
<input type="hidden" id="bno" name="bno"
value='<c:out value="${board.bno}"/> '>
</form>
...
브라우저에서는 <form> 태그의 내용은 보이지 않고 버튼만이 보이게 된다.
get.jsp
...
<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>
...
사용자가 수정 버튼을 누르는 경우에는 bno 값을 같이 전달하고 <form> 태그를 submit시켜서 처리한다.
만일 사용자가 list로 이동하는 경우에는 아직 아무런 데이터도 필요하지 않으므로 <form> 태그 내의 bno 태그를 지우고 submit을 통해서 리스트 페이지로 이동한다.
수정 페이지에서 링크 처리
수정 페이지에서는 사용자가 다시 목록 페이지로 이동할 수 있도록 하기 위해서 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') {
formObj.attr("action", "../board/list").attr("method", "get");
formObj.empty();
}
formObj.submit();
});
});
</script>
수정된 내용은 클릭한 버튼이 List인 경우 action속성과 method 속성을 변경한다.
/board/list로의 이동은 아무런 파라미터가 없기 때문에 <form> 태그의 모든 내용은 삭제한 상태에서 submit()을 진행한다.이후에 코드는 실행되지 않도록 return을 통해서 제어 한다.

'Spring Framework' 카테고리의 다른 글
| 스프링 MVC 프로젝트 - MyBatis와 스프링에서 페이징 처리 (0) | 2021.07.19 |
|---|---|
| 스프링 MVC 프로젝트 - 오라클 데이터베이스 페이징 처리 (0) | 2021.07.18 |
| 스프링 MVC 프로젝트 - 프레젠테이션(웹)계층의 CRUD 구현 (0) | 2021.07.08 |
| 스프링 MVC 프로젝트 (0) | 2021.07.06 |
| 스프링 MVC 프로젝트 (0) | 2021.07.06 |
