관리 메뉴

Just Do it

Part 3) 기본적인 웹 게시물 관리 (Ch 11) 본문

신입 개발자가 되기 위해 공부했던 독학 자료들/코드로 배우는 스프링 웹프로젝트

Part 3) 기본적인 웹 게시물 관리 (Ch 11)

Seojoo21 2022. 2. 17. 19:20

* 이 카테고리의 글은 <코드로 배우는 스프링 웹 프로젝트> 에서 각 파트별로 진행하는 프로젝트의 흐름을 보기 위해 각 장의 내용을 간단히 요약한 것이다.

 

* 출처: 코드로 배우는 스프링 웹 프로젝트 개정판, 구멍가게 코딩단, 남가람북스  

 

Ch11. 화면 처리

- 화면에는 JSP, JavaScript(jQuery), CSS, HTML을 이용해서 작성한다.

- 화면을 개발하기 전에는 반드시 화면의 전체 레이아웃이나 디자인이 반영된 상태에서 개발하는 것을 추천한다. 

- Bootstrap : 무료 디자인 이용 가능

https://startbootstrap.com

 

Free Bootstrap Themes, Templates, Snippets, and Guides - Start Bootstrap

Landing Page A clean, functional landing page theme

startbootstrap.com

 

11.1) 목록 페이지 작업과 includes 

- 게시물 리스트의 URL은 '/board/list'이므로 src/main/webapp/WEB-INF/views에 board 폴더를 만들고 그 안에 list.jsp 파일을 추가한다. 

- 톰캣 경로는 편리하게 '/'로 조정한다.

 

11.1.1) SB Admin2 페이지 적용하기 

- Bootstrap에서 다운받은 무료 템플릿 파일의 tables.html의 내용을 list.jsp의 내용으로 그대로 복사해서 수정하고 실행한다. 

- 브라우저에서는 CSS 등이 완전히 깨진 상태이므로 텍스트만 출력되는 것을 볼 수 있다.

- CSS와 JS파일들의 경로를 수정하는 작업은 브라우저의 개발자 도구를 통해서 확인하며 진행한다. 개발자 도구를 통해서 현재 브라우저의 Network 부분을 확인하고, 페이지를 '새로고침'하면 잘못된 URL의 정보를 확인할 수 있다. 

- SB Admin2의 CSS의 경로는 현재 프로젝트에서는 제대로 서비스 될 수 없다. 

- SB Admin2의 압축을 풀어둔 모든 폴더를 프로젝트 내 src/main/webapp/resources 폴더로 복사해 넣는다.

- 파일들을 resources 경로로 넣어도 아직은 페이지에서 경로를 수정하지 않았기 때문에 문제가 생기는 것은 동일하다. list.jsp 파일에서 CSS와 JS 파일의 경로를 '/resources'로 시작하도록 수정한다. 

 

11.1.2) includes 적용 

- JSP를 작성할 때마다 많은 양의 HTML 코드를 이용하는 것을 피하기 위해 JSP의 include 지시자를 활용해서 페이지 제작 시에 필요한 내용만을 작성할 수 있게 사전에 작업을 해야한다.

- 프로젝트 views 폴더에 includes 폴더를 작성하고, header.jsp와 footer.jsp를 선언한다.

 

<header.jsp 적용>

- header.jsp는 페이지에서 핵심적인 부분이 아닌 영역 중에서 위쪽의 HTML 내용을 처리하기 위해서 작성한다.

- 브라우저에서 '검사' 기능을 활용하면 특정안 <div>가 어떤 부분을 의미하는지 확인할 수 있다. 

- 게시판이 들어갈 핵심적인 페이지의 내용 부분 위까지를 잘라서 header.jsp의 내용으로 처리한다. 

- header.jsp로 처리하는 부분을 list.jsp 파일 내에 아래 코드로 처리하여 다시 브라우저에서 화면이 깨지지 않는지 확인한다.

<%@include file="../includes/header.jsp" %>

 

<footer.jsp 적용>

- header.jsp 적용 처리를 했던 것과 마찬가지로 게시판이 들어갈 핵심적인 페이지의 내용의 아래 부분을 잘라서 footer.jsp의 내용으로 처리한다.

- footer.jsp로 처리하는 부분을 list.jsp 파일 내에 아래 코드로 처리하여 다시 브라우저에서 화면이 깨지지 않는지 확인한다.

<%@include file="../includes/footer.jsp" %>

 

11.1.3) jQuery 라이브러리 변경 

- JSP 페이지를 작성하다보면 JavaScript로 브라우저 내에서의 조작이 필요한 경우가 많다. 예제는 jQuery를 이용할 것인데 이때 jQuery를 header.jsp에 선언해두면 작성하는 JSP에서 자유롭게 사용할 수 있다.

- jQuery 최신 버전 사용할 수 있는 태그: (이 태그를 header.jsp의 마지막 부분에 입력해준다.)

<script  src="http://code.jquery.com/jquery-latest.min.js"></script>

- 해당 프로젝트에서 사용하는 jQuery 태그: (위의 최신 버전 사용 태그는 모달창이 출력되지 않는 에러가 발생한다.) 

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

 

11.2) 목록 화면 처리 

- list.jsp에 JSTL의 출력과 포맷을 적용할 수 있는 태그 라이브러리를 추가한다. 

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>

- Oracle SQL Developer에서 만들었던 테이블의 칼럼을 떠올리며 <table class=> 태그를 이용해 게시판 틀을 만든다.

 

11.2.1) Model에 담긴 데이터 출력

- '/board/list'를 실행했을 때 이미 BoardController는 Model을 이용해서 게시물의 목록을 'list'라는 이름으로 담아서 전달했으므로 list.jsp에서는 이를 출력한다. 

- 출력은 JSTL을 이용해서 처리한다. (<c:forEach> 태그 사용) 

- list.jsp 내 <tbody> 태그와 각 <tr>을 작성한다. 

 

 ...
 
 <div class="card mb-4">
                            <div class="card-header">
                                <i class="fas fa-table me-1"></i>
                                Board List Page
                            </div>
                            <div class="card-body">
                               <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>
                       
                               </table>
                            </div>
                        </div>

 

11.3) 등록 입력 페이지와 등록 처리

- 게시물의 등록 작업은 POST 방식으로 처리하지만, 화면에서 입력을 받아야 하므로 GET 방식으로 입력 페이지를 볼 수 있도록 BoardController에 메서드를 추가한다. 

- GET방식으로 연결한 register()는 입력 페이지를 보여주는 역할만을 하기 때문에 별도의 처리가 필요하지 않다.

- views 폴더에는 includes를 적용한 입력 페이지 register.jsp 를 작성한다.  

- register.jsp 페이지에서는 <form> 태그를 이용해서 필요한 데이터를 전송한다. 

- <input>이나 <textarea> 태그의 name 속성은 BoardVO 클래스의 변수와 일치시켜준다.

- 브라우저를 통해 '/board/register' 화면이 제대로 출력되는지 확인한다. 

 

11.3.1) 한글 문제와 UTF-8 필터 처리

- web.xml에 한글 변환 필터를 추가해준다. (아래 포스팅 참고) 

https://seojoo21.tistory.com/46?category=995432 

 

스프링 웹 출력시 한글 깨짐 방지를 위해 web.xml 파일에 입력하면 되는 <filter> 태그

** src/main/webapp/WEB-INF에 위치한 web.xml 파일에 아래 코드를 추가하여 저장해준다. encodingFilter org.springframework.web.filter.CharacterEncodingFilter encoding UTF-8 forceEncoding true encodingFi..

seojoo21.tistory.com

11.3.2) 재전송(redirect) 처리 

- BoardController에서 register() 메서드(등록 작업을 위한 POST 방식)는 return "redirect:/board/list"; 를 전송하는데 브라우저는 이를 통보받은 후 '/board/list'로 이동하게 된다.

- 만일 이와 같은 재전송을 하지 않는다면 사용자는 브라우저의 '새로고침'을 통해서 동일한 내용을 계속 서버에 등록할 수 있기 때문에 흔히 도배라고 표현하는 문제가 발생하게 된다.

- 따라서 등록, 수정, 삭제 작업은 처리가 완료된 후 다시 동일한 내용을 전송할 수 없도록 아예 브라우저의 URL을 이동하는 방식을 이용한다.

- 이러한 과정에서 하나 더 신경 써야 하는 것은 브라우저에 등록, 수정, 삭제의 결과를 바로 알 수 있게 피드백을 줘야 한다는 것이다.

- 이러한 피드백 작업은 경고창이나 <div>를 이용하는 모달창을 이용해서 처리한다.

 

- BoardController에서 redirect 처리를 할 때 RedirectAttributes라는 특별한 타입의 객체를 이용했다. 

- addFlashAttribute()의 경우 이러한 피드백 작업 처리에 적합한데, 그 이유는 일회성으로만 데이터를 전달하기 때문이다.

- list.jsp 페이지의 아래쪽에 <script> 태그를 이용해서 상황에 따른 메세지를 확인할 수 있다.

<script type="text/javascript">
$(document).ready(function(){
	var result = '<c:out value="${result}"/>';
});
</script>

 

11.3.3) 모달(Modal)창 보여주기

- BootStrap은 모달창을 간단하게 사용할 수 있으므로 목록 화면에서 필요한 메세지를 보여주는 방법을 사용해보자

- 모달창은 기본적으로 <div>를 화면에 특정 위치에 보여주고, 배경이 되는 <div>에 배경색을 입혀서 처리한다. 모달창은 활성화된 <div>를 선택하지 않고는 다시 원래의 화면을 볼 수 없도록 막기 때문에 메세지를 보여주는데 효과적인 방식이다.

- 모달창을 처리하기 위해서는 우선 <div>를 이용해서 페이지의 코드에 추가해야한다.

- list.jsp 내에 <table>태그의 아래쪽에 모달창의 <div>를 추가한다.

* 코드는 깃헙에 올려놨으니 깃헙 내 list.jsp 를 확인할 것.

 

11.3.4) 목록에서 버튼으로 이동하기 

- 게시물의 작성과 목록 페이지로 이동이 정상적으로 동작했다면, 마지막으로 목록 페이지 상단에 버튼을 추가해서 등록 작업을 시작할 수 있게 처리해야 한다.

- list.jsp의 HTML 구조에 버튼을 만들어주는 태그를 넣어주고, 하단의 jQuery를 이용하는 부분에서 해당 버튼을 클릭했을 때의 동작을 정의한다. 

 

11.4) 조회 페이지와 이동

- 게시물의 등록과 리스트 처리가 끝났다면 가장 중요한 틀은 완성되었다고 볼 수 있다.

- 다음으로는 목록 페이지에서 링크를 통해 GET 방식으로 특정한 번호의 게시물을 조회할 수 있는 기능을 작성한다.

 

11.4.1) 조회 페이지 작성

- 조회 페이지는 입력 페이지와 거의 유사하지만 게시물 번호(bno)가 출력된다는 점과 모든 데이터가 읽기 전용으로 처리된다는 점이 가장 큰 차이이다.

- views/board 폴더 내 get.jsp를 register.jsp를 복사해서 작성한다.

 

- get.jsp는 게시물 번호를 보여줄 수 있는 필드를 추가하고, 모든 데이터는 readonly를 지정해서 작성한다. 

- register.jsp에 있던 <form> 태그는 조회 페이지에서 필요하지 않으므로 제거하는 대신 마지막에는 수정/삭제 페이지로 이동하거나 원래의 목록 페이지로 이동할 수 있는 버튼을 추가한다. 

- 브라우저에는 'board/get?bno=1'과 같이 게시물의 번호를 반드시 파라미터로 전달해서 테스트 해본다.

- 테스트 후 화면 하단의 버튼에 '/board/list'와 '/board/modify?bno=xx'와 같이 이동하는 링크를 추가한다. 

 

11.4.2) 목록 페이지와 뒤로 가기 문제 

- 목록 페이지에서 각 게시물 제목에 <a> 태그를 적용해서 조회 페이지로 이동하게 처리한다. 

- 최근 웹페이지들은 사용자들의 트래픽을 고려해 목록 페이지에서 새창을 띄워 조회 페이지로 이동하는 방식을 선호하지만, 전통적인 방식에서는 현재창 내에서 이동하는 방식을 사용한다.

- '뒤로 가기'의 해결은 조금 어려운 내용들이 나올 수 있으므로 학습에 어려움이 있다면 건너뛰는 선택도 나쁘지 않다. 

(하지만 나는 해볼거다! 파이팅)

 

<목록에서 조회 페이지로의 이동>

- 제목을 클릭하면 조회 페이지로 넘어갈 수 있도록 list.jsp 페이지의 제목(title) 코드에 <a href=""> 링크 코드를 추가해서 list.jsp 페이지를 조금 수정한다.

- 그럼 브라우저를 통해 화면을 확인해보면 각 게시물의 제목에 링크가 걸리는 것을 확인할 수 있고, 제목을 클릭하면 정상적으로 조회 페이지로 이동하는 것을 볼수 있다. 

- 조회 페이지로의 이동은 JavaScript를 이용할 수도 있고, 위와 같이 직접 <a> 태그를 이용해서도 처리가 가능하다.

- 만일 조회 페이지를 이동하는 방식이 아니라 '새창'을 통해 보고 싶다면 <a> 태그의 속성으로 target='_blank'를 지정하면 된다. 

*<a> 태그와 <form> 태그에는 target 속성을 지정할 수 있는데 '_blank'는 새로운 창에서 처리 된다. 

 

<뒤로 가기의 문제> * 특히나 이 부분은 나중에 다시 꼭 교재를 읽어봐야한다. 

- 게시물 '등록-> 목록 -> 조회' 까지는 순조롭지만 브라우저의 '뒤로 가기'를 선택하는 순간, 다시 게시물의 등록 결과를 확인하는 모달창이 나오는 문제가 있다. 

- 이러한 문제가 생기는 원인은 브라우저에서 '뒤로 가기'나 '앞으로 가기'를 하면 서버를 다시 호출 하는 것이 아니라 과거에 자신이 가진 모든 데이터를 활용하기 때문이다. 

- 브라우저에서 조회 페이지와 목록 페이지를 여러 번 앞으로 혹은 뒤로 이동해도 서버에서는 처음에 호출을 제외하고 별다른 변화가 없는 것을 확인할 수 있다.

- 이 문제를 해결하려면 window의 history 객체를 이용해서 현재 페이지는 모달창을 띄울 필요가 없다고 표시를 해 두는 방식을 이용해야 한다. 

- window의 history 객체는 스택 구조로 동작한다. (교재 256쪽 참고하기) 

- window.history 객체를 이용하여 list.jsp 하단에 있는 모달창의 JavaScript를 수정한다. 

* 코드는 깃헙에 올려놨으니 깃헙 내 list.jsp 를 확인할 것.

- 모달창이 보이는 여부와 관계없이 JavaScript의 모든 처리가 끝나게 되면 history에 쌓이는 상태는 모달창을 보여줄 필요가 없는 상태가 된다.

 

11.5) 게시물의 수정/삭제 처리 

- 게시물의 수정 작업은 일반적으로 다음과 같다.

1) 조회 페이지에서 직접 처리하는 방식

2) 별도의 수정/삭제 페이지를 만들어서 해당 페이지에서 수정과 삭제를 처리하는 방식

- 최근에는 게시물의 조회 페이지에서 댓글 등에 대한 처리가 많아지면서 수정과 삭제는 별개의 페이지에서 하는 것이 일반적이다.

- 조회 페이지에서는 GET 방식으로 처리되는 URL을 통해서 수정/삭제 버튼이 존재하는 화면을 볼 수 있게 제작해야한다.

- 수정 혹은 삭제 작업은 POST 방식으로 처리되고, 결과는 다시 목록 화면에서 확인할 수 있는 형태로 제작한다. 

 

11.5.1) 수정/삭제 페이지로 이동

- BoardController에서 수정/삭제가 가능한 화면으로 이동하는 것은 조회 페이지와 같다.

- BoardController에 기존의 get() 메서드를 조금 수정하여 화면을 구성한다. 

- 브라우저에서는 '/board/modify?bno=30'과 같은 방식으로 처리하므로, views 폴더 내에 modify.jsp를 작성한다. 

- modify.jsp는 get.jsp와 같지만 수정이 가능한 제목이나 내용등이 readonly 속성이 없도록 작성한다. 

- POST방식으로 처리하기 위해 <form> 태그로 내용들을 감싸게 한다. 

- 등록일과 수정일이 나중에 BoardVO로 수집되어야 하므로 날짜 포맷을 'yyyy/MM/dd'로 하여 추가한다. (날짜 포맷 일치 해야함)

- 수정,삭제,목록으로 돌아가기 버튼을 추가한다. 

- 브라우저에서 'http://localhost:8080/board/modify?bno=19'와 같이 게시물 번호를 이용해서 수정 페이지가 정상적으로 출력되는지 확인한다. 

- JavaScript에서는 위의 버튼에 따라서 다른 동작을 할 수 있도록 수정해야 한다. 

* 코드는 깃헙에 올려놨으니 깃헙 내 modify.jsp 를 확인할 것.

 

11.5.2) 게시물 수정/삭제 확인

- 브라우저에서 잘 작동하는지 확인한다. 

 

11.5.3) 조회 페이지에서 <form> 처리 

- 게시물의 조회 페이지에서는 수정과 삭제가 필요한 페이지로 링크를 처리해야한다. 앞에서 list.jsp의 경우 직접 제목 버튼에 링크를 처리하는 방식을 사용하여 처리했지만, 나중에 다양한 상황을 처리하기 위해 get.jsp 에서 <form> 태그를 이용해서 수정해보자.

- 사용자가 버튼을 클릭하면 operForm이라는 id를 가진 <form> 태그를 전송해야 하므로 get.jsp 페이지 하단에 추가적인 JavaScript 처리가 필요하다. 

 

11.5.4) 수정 페이지에서 링크 처리 

- modify.jsp 페이지에서는 사용자가 다시 목록 페이지로 이동할 수 있도록 하기 위해서 JavaScript 내용을 조금 수정한다.

- 수정된 내용: 클릭한 버튼이 List인 경우 action 속성과 method 속성을 변경한다. 

 

수정 전:

<!-- 수정, 삭제, 목록으로 돌아가기 버튼별 동작을 자바스크립트로 설정       -->  
<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>

 

 

수정 후: 

<!-- 수정, 삭제, 목록으로 돌아가기 버튼별 동작을 자바스크립트로 설정       -->  
<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(); // 'board/list'로의 이동은 아무런 파라미터가 없기 때문에 <form>태그의 모든 내용은 삭제한 상태에서 submit()을 진행한다. 
		}
		formObj.submit();
	});
});

</script>