본문 바로가기
Lecture/Spring

[본격 게시판짜기 Part3.8 Spring MVC] rest-ful 서비스에서 정적 자원 사용

by cusmaker 2012. 11. 4.
반응형

2012/06/13 - [Lecture/HTML] - [본격 게시판짜기 Part1.1 - 게시판도 HTML부터] 게시글 리스트

2012/06/13 - [Lecture/HTML] - [본격 게시판짜기 Part1.2 - 게시판도 HTML부터] 글입력폼

2012/06/13 - [Lecture/Javascript-기초] - [본격 게시판짜기 Part1.3 - Dom 맛보기 ] 글입력폼 검사

2012/06/13 - [Lecture/Jsp] - [본격 게시판짜기 Part1.4 - HTML-> JSP] form 파라미터 받기

2012/06/26 - [Lecture/SQL / Oracle] - [본격 게시판짜기 Part1.5 JSP > Oracle] 게시판 DB 테이블 생성

2012/06/26 - [Lecture/Jsp] - [본격 게시판짜기 Part1.6 Oracle > JSP] Database 접속 및 Select

2012/07/05 - [Lecture/Jsp] - [본격 게시판짜기 Part1.7 JSP, SQLDeveloper] 게시글 입력 및 리스트조회기능

2012/07/08 - [Lecture/Jsp] - [본격 게시판짜기 Part1.8 JSP 게시글 조회] 게시글 조회기능 및 게시글 삭제

2012/07/10 - [Lecture/Jsp] - [본격 게시판짜기 Part2.1 Model2 MVC패턴] 뷰(View) 코드 분리

2012/07/10 - [Lecture/Jsp] - [본격 게시판짜기 Part2.2 Model2 MVC패턴] Entity Beans의 사용

2012/07/12 - [Lecture/Jsp] - [본격 게시판짜기 Part2.3 Model2 MVC패턴] Controller구성

2012/07/13 - [Lecture/Jsp] - [본격 게시판짜기 Part2.4 Model2 MVC패턴] Model 구성

2012/07/16 - [Lecture/Mybatis] - [본격 게시판짜기 Part2.5 Model2 MVC패턴] Model 구성2 - i-batis의 사용

2012/07/17 - [Lecture/Mybatis] - [본격 게시판짜기 Part2.6 Model2 MVC패턴] Model 구성2 - i-batis의 사용2

2012/07/21 - [Lecture/Mybatis] - [본격 게시판짜기 Part2.7 Model2 MVC패턴] i-batis의 queryForList 활용 Paging기능구현

2012/07/22 - [Lecture/Jsp] - [본격 게시판짜기 Part2.8 Model2 MVC패턴] count기능추가

2012/07/28 - [Lecture/Jsp] - [본격 게시판짜기 Part2.9 Model2 MVC패턴] 파일업로드 기능추가

2012/07/28 - [Lecture/Jsp] - [본격 게시판짜기 Part2.10 Model2 MVC패턴] 파일다운로드/삭제 기능추가

2012/07/31 - [Lecture/Jsp] - [본격 게시판짜기 Part3.1 Spring MVC] Ajax란?

2012/08/28 - [Lecture/Jsp] - [본격 게시판짜기 Part3.2 Spring MVC] lightbox 적용하기

2012/09/22 - [Lecture/Spring] - [본격 게시판짜기 Part3.3 Spring MVC] Spring MVC 개요 및 설정하기

2012/09/22 - [Lecture/Spring] - [본격 게시판짜기 Part3.4 Spring MVC] Spring MVC 개요 및 설정하기2

2012/10/13 - [Lecture/Spring] - [본격 게시판짜기 Part3.5 Spring MVC] SqlMapClientDaoSupport활용, Model 변경하기

2012/10/29 - [Lecture/Spring] - [본격 게시판짜기 Part3.6 Spring MVC] multipartResolver를 활용한 파일업로드 변경하기

2012/10/29 - [Lecture/Spring] - [본격 게시판짜기 Part3.7 Spring MVC] JSON 요청이란?


안녕하세요 cocy입니다.

이번포스팅에서는 지금까지 작성한 테이블에 디자인을 입혀보고,

미진한 기능들에 대한 보완과 함께 "본격 게시판짜기" 포스팅을 마지막으로 하려 합니다.


그럼 마지막 포스팅 시작합니다.


테이블을 디자인 하기전에, rest-ful 서비스에서는 정적인 자원에 대한 요청을 어떤식으로 해야하는지 설명드리겠습니다.

여기서 말하는 정적인 자원들이란, 컴파일이 필요로 하지 않은 즉, 변동되지 않는 자원들

순수 CSS파일이나 순수 JS(자바스크립트)파일, 또는 이미지와 같은 파일들을 의미합니다.



기존에 Model1이나 Model2파트를 진행할때에는 이러한 자원들을 요청하려면 

단순히 서버에 요청을 날리는 것으로 (ex - http://localhost/board/image/aaa.jpg) 자원을 사용 할 수가 있었는데요,


rest-ful 서비스를 사용하게되면 기본적으로 url의 모든 요청 패턴이 컨트롤러에서 인식되어 정적인 자원을 요청 할 수 없습니다.

정확히 말하면 현재 설정한 web.xml파일을 보시면

 '/' 로 들어오는 모든 요청이 rest-ful요청으로 맵핑되도록 설정을 해놓았기때문인데요


이를 스프링에서는 별도의 설정변경을 통하여

특정 URL의 패턴이 인식되면 정적인 자원의 요청으로 판단하여 해당요청을 수행할 수가 있습니다.


보실 부분은 dispatcher-servlet.xml (spring.board.config) 입니다.

<!-- resources -->

<mvc:resources mapping="/resources/**" location="/resources/"/> 


바로 mvc:resources 부분인데요, 

해당 태그를 쓰고 location에 해당하는 곳에 정적자원들을 위치하고, 

mapping 속성에는 요청 URL의 패턴을 명시해줍니다.

즉, " /resources/**" 로 시작하는 모든 요청들은 정적인 자원을 요청하는 URL로 인식하고,

" /resoruces/ "폴더에서 해당 자원을 찾습니다.


여기서 " /resoruces/ "폴더의 경로는 WebContent를 기준으로 합니다.

정적인 자원들은 WEB-INF 폴더 하위에 위치하는것은 불가능합니다.

WebContent 폴더 밑에 resoruces 폴더를 생성하시면 됩니다.


그리고 나서 테이블 CSS를 해볼텐데요,

직접 테이블 디자인말고 오픈된 테이블 CSS를 적용해 보도록 합시다. 다음사이트를 참고하세요

http://icant.co.uk/csstablegallery/tables/79.php



원하는 테이블디자인을 확인하고 다운로드를 누르면 해당 디자인의 CSS 소스를 구하실 수 있습니다.

이 소스를 css파일로 만들어서 resources 폴더 안에 위치시켜줍니다. 필요한 이미지파일도 함께 있어야겠죠?

이후, 소스상에서 해당 css파일을 사용하기위해 <link> 태그를 이용해 주소를 맵핑하는데요,

여기서 정적인 자원의 요청을 하기위해 

href속성에는 <c:url> 이라는 jstl의 커스텀 태그를 활용하여 /resources/..로 시작하는

url을 적어줍니다. 다음 그림을 보고 이해하시면 되겠습니다.



그리고 나서 가져온 css를 기존소스에 반영하기위해 몇가지 수정할텐데요,


th 태그가 들어있는 tr태그를 감싸는 <thead> 태그를 추가하고,

th태그들에 너비를 고정하기위해 퍼센테이지로 너비를 잡아주었습니다.(총합은 100%여야겟죠?)

컨텐츠를 가운데에 표시하기위해 <div id="content">태그를 이용하여 감싸주었습니다.

이후 content에 대해 css속성을 살짝 입혀 너비를 잡아주고 가운데로 표시한뒤,

더보기 버튼은 오른쪽에 배치하였습니다. 여기서 a태그였던 것을 span태그로 바꿔 클릭시 스크롤이 위로가지 않도록 하였습니다.

그리고 첨부파일란을 추가하여 첨부파일이 있으면 다운가능한 링크를 걸었습니다.

소스를 보시면 다음과 같습니다. list.jsp의 전체 소스입니다.

<%@ page language="java" contentType="text/html; charset=EUC-KR"

    pageEncoding="EUC-KR"%>

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


<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=EUC-KR">

<title>본격! 게시판 - 게시글 리스트</title> <!-- 윈도우 상단에 뜨는 내용입니다. -->

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7/jquery.js"></script>


<script type="text/javascript">

function openContent(idx){

$('.mw_layer').addClass('open');

$.ajax({

  type:'post',

  url:'/board/count',

  data: ({idx:idx}),

  success:function(data){

$('#layer').html(data);

  }

});

}

function closeContent(){$('.mw_layer').removeClass('open');}

jQuery(function($){

var layerWindow = $('.mw_layer');

// ESC Event

$(document).keydown(function(event){

 if(event.keyCode != 27) return true;

 if (layerWindow.hasClass('open')) {

  layerWindow.removeClass('open');

 }

 return false;

});

// Hide Window

layerWindow.find('>.bg').mousedown(function(event){

 layerWindow.removeClass('open');

 return false;

});

//$("tr:even").addClass("odd");


});

function loadNextPage(){

var page = $('#page').val();

page = parseInt(page);

page += 10;

$.ajax({

  type:'post',

  url:'/board/ajaxList',

  data: ({page:page}),

  success:function(data){

  if(data.trim() == "")

  alert("더이상 게시글이 없습니다.");

  else{

  $('table').append(data);

  $('#page').val(page);

  }

  }

});

}

function loadWriteForm(){

$.ajax({

  type:'post',

  url:'/board/write',

  success:function(data){

  $('.mw_layer').addClass('open');

  $('#layer').html(data);

  }

});

}

</script>

<style type="text/css">

html, body{height:100%;margin:0 auto;}

div#content {margin : 0 auto; width:800px;}

.mw_layer{display:none;position:fixed;_position:absolute;top:0;left:0;z-index:10000;width:100%;height:100%}

.mw_layer.open{display:block}

.mw_layer .bg{position:absolute;top:0;left:0;width:100%;height:100%;background:#000;opacity:.5;filter:alpha(opacity=50)}

#layer{position:absolute;top:40%;left:40%;width:400px;height:400px;margin:-150px 0 0 -194px;padding:28px 28px 0 28px;border:2px solid #555;background:#fff;font-size:12px;font-family:Tahoma, Geneva, sans-serif;color:#767676;line-height:normal;white-space:normal}

</style>

<link rel="stylesheet" type="text/css" href="<c:url value="/resources/style/table.css"/>" />

</head>

<body> <!-- HTML문서의 주 내용이 들어가는 부분입니다. -->

<div id="content">

<table>

<caption onclick="location.href='/board/'" style="cursor:pointer">본격 게시판</caption>

<thead>

<tr> <!-- table태그 내에서 행을 정의할때 쓰는 태그입니다. -->

<th width="7%">번호</th> <!-- Table Header의 약자로 table태그 내에서 -->

<th width="50%">제목</th> <!-- 강조하고싶은 컬럼을 나타낼때 쓰는 태그입니다. -->

<th width="16%">작성자</th>

<th width="10%">날짜</th>

<th width="7%">조회</th>

<th width="10%">첨부파일</th>

</tr>

</thead> 

<c:forEach items="${articleList}" var="article">

<tr>

<td>${article.idx}</td>

<td><a href="#layer" onclick="openContent('${article.idx}')">${article.title} </a></td>

<td>${article.writer}</td>

<td>${article.regdate}</td>

<td>${article.count}</td>  

<td>

<c:if test="${empty article.filename}">없음</c:if>

<c:if test="${not empty article.filename}"><a href="/board/download/${article.idx}">다운로드</a></c:if>

</td>  

</tr>

</c:forEach>

</table>  

<input type="hidden" name="page" id="page" value="${page}">

<a href="#" onclick="loadWriteForm()">글쓰기</a>

<span style="cursor: pointer;float: right;" onclick="loadNextPage()">더보기</span>

</div>

<!-- light box -->

<div class="mw_layer">

<div class="bg"></div>

<div id="layer"></div>

</div>

</body>

</html>

추가되거나 변경된 내용은 빨간색으로 표시해 두었습니다.


그럼 결과화면을 한번 볼까요?


상당히 깔끔해졌죠? 익스플로러에서도 깨지는 부분이 없는지 한번 확인해 보시기바랍니다.

그것이 바로 표준을 지키는 웹사이트 구축과 크로스브라우징의 시작입니다.


다음으로는 컨텐츠내용을 확인하는 lightbox안의 내용도 디자인 해보았습니다.


이 페이지 역시 테이블의 너비를 잡아주고, 같은 css를 공유하도록 하였습니다.

소스는 다음과 같습니다.

content.jsp

<%@ page language="java" contentType="text/html; charset=EUC-KR"

    pageEncoding="EUC-KR"%>

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

<script>

function deleteArticle(idx){

$.ajax({

  type:'post',

  url:'/board/delete.json',

  data: ({idx:idx}),

  success:function(data){

  console.log(data);

 if(data.status == "success"){

 alert("삭제되었습니다.");

 location.reload();

 }

  }

});

}

</script>

<table style="width: 580px;">

<caption>게시글 조회</caption>

<tr>

<th>번호</th>

<td>${article.idx}</td>

<th>작성자</th>

<td>${article.writer}</td>

<th>등록일</th>

<td>${article.regdate}</td>

</tr>

<tr>

<th>제목</th> <!-- colspan은 행병합 속성입니다. -->

<td colspan="3">${article.title}</td>

<th>조회수</th>

<td>${article.count}</td>

</tr>

<tr>

<th height="300px;">내용</th>

<td  colspan="5"><pre>${article.content}</pre></td>

</tr>

<tr>

<th>첨부파일</th>

<td  colspan="5">

<c:if test="${empty article.filename}">없음</c:if>

<c:if test="${not empty article.filename}"><a href="/board/download/${article.idx}">${article.filename}</a></c:if>

</td>

</tr>

</table>

<a href="#" onclick="deleteArticle('${article.idx}')">게시글삭제</a>

<a href="#" onclick="closeContent()" style="float: right;">목록으로</a> 



다음은 글쓰기 페이지도 해야겠죠?


소스는 다음과 같습니다.

write.jsp

<%@ page language="java" contentType="text/html; charset=EUC-KR"

    pageEncoding="EUC-KR"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=EUC-KR">

<title></title>

<script>

function formCheck() { 

var title = document.forms[0].title;       

var writer = document.forms[0].writer;

var regdate = document.forms[0].regdate;

var content = document.forms[0].content; 


if (title.value == null || title.value == ""){                                   

alert('insert title.');                                  

document.forms[0].title.focus();                          

return false;                                                     

}

if (writer.value == null ||  writer.value  == ""){           

alert('insert writer');  

document.forms[0].writer.focus();                       

return false;                

}else if(writer.value.match(/^(\w+)@(\w+)[.](\w+)$/ig) == null){

alert('wrong fomat. please E-mail format');  

document.forms[0].writer.focus();                       

return false;  

}

if (content.value == null || content.value == ""){                                   

alert('insert content.');                                  

document.forms[0].content.focus();                          

return false;                                                     

}

/*

if (regdate.value == null || regdate.value == "" ){                             

alert('insert date');    

document.forms[0].regdate.focus();                       

return false;             

}else if(regdate.value.match(/^\d\d\d\d\d\d+$/ig)   == null){

alert('wrong format. please insert Number(6)');  

document.forms[0].regdate.focus();                       

return false;  

}

*/

}

</script>

</head>

<body>

<form action="/board/insert" method="post" enctype="multipart/form-data" onsubmit="return formCheck();">

<table style="width: 580px;">

<caption>글쓰기</caption>

<tr>

<th>작성자</th>

<td><input type="text" name="writer" size="20" /></td>

</tr>

<tr>

<th>제목</th>

<td><input type="text" name="title" size="60" /></td>

</tr>

<tr>

<th height="300px">내용</th>

<td><textarea rows="18" cols="60" name="content"></textarea></td>

</tr>

<tr>

<th>첨부파일</th>

<td><input type="file" name="file"></td>

</tr>

</table>

<input type="submit" value="글쓰기" style="float: right;"/>

</form> 


</body> 

</html>   


이것으로 디자인도 마치도록 하겠습니다.

미진한 부분이 많지만 여러분께서는 좀더 연구하여 완성도를 높여 보시기 바랍니다.


이것으로 본격 게시판짜기 세파트중 마지막 파트를 끝냅니다.

개인적으로 스프링에 관한 강좌의 내용과 설명이 많이 부족한 것을 느끼고 있습니다. 

그간 포스팅하면서 그만큼 제가 부족하다는 것을 느끼는 계기가 된것 같습니다.

더 열심히 습득해서 보완해 나가도록 하겠습니다.


지금까지 작업한 소스는 네이버 개발자 센터 svn주소

https://dev.naver.com/svn/board1/board3

anonsvn/anonsvn 

에서 임포스 하실 수 있으며,

네이버 SVN서비스가 종료되어 늦었지만 지금이라도 github으로 대체합니다. 

github : https://github.com/zuhthebeg/board3


데이터베이스 스키마는 part 3.1을 참고하시기 바랍니다.


이때까지 부족한 포스팅 내용들을 보며 잘 따라와 주신 분들께 감사드리며,

구현하시면서 질문이나 어려운점 있으시면 언제든 메일이나 댓글로 남겨주시면 같이 고민해 드리겠습니다.


그동안 수고하셨습니다. 


다음강의로는 번외로 전자정부프레임워크에 관해 포스팅할 계획입니다. 다른 강좌들도 많이 찾아주세요!