Java EE 정리 14
18 Mar 2019
Reading time ~9 minutes
Java EE 정리 14 - JSTL(2), 달력 만들기
JSTL
- redirect
- 비정상적인 요청이 발생했을 때 페이지 이동 기술
<c:redirect url="이동할 페이지 URL"/>
- import
- 공통 코드가 아닌 디자인인 경우 사용
- 지시자의 충돌이 발생하지 않는다
- 변수나 method가 공유되지 않는다
<c:import url="끼워넣을 JSP URL">
- import와 동일 기능을 가진, jsp include 액션태그
- 공통코드를 가진 JSP를 분리하여 작성하고 필요한 곳에서 사용하기 위해 사용되는 기술
- include directive는 공통 코드를 합칠 때, jsp include 액션태그, JSTL import는 디자인을 합칠 때 사용
<%@ include file="..." %> <!-- 공통 코드인 경우, ex) 세션체크 -->
<jsp:include page="..." > <!-- 디자인인 경우 -->
- 예외처리 catch
<c:catch var="예외객체명">
예외발생 예상코드
</c:catch>
<c:if test="${ 예외객체명 ne null }"> <!-- 'not empty 객체명'으로 조건바꿀 수 있다 -->
예외발생 시 제공할 코드
</c:if>
- redirect 사용 예
<!-- jstl_redirect_a.jsp -->
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
...
정상적인 경우 제공할 서비스<br/>
<img src="../common/images/img2.jpg"/>
<%
boolean flag = new Random().nextBoolean();
pageContext.setAttribute("flag", flag);
%>
<c:if test="${ flag }">
<!-- 비정상적인 경우의 요청이 있을 때 페이지 이동 -->
<c:redirect url="jstl_redirect_b.jsp"></c:redirect>
</c:if>
<!-- jstl_redirect_b.jsp -->
...
비정상적일 때 응답할 페이지<br/>
<img src="../common/images/img.png"/>
...
- import 사용 예
<!-- jstl_import.jsp -->
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
...
외부페이지<br/>
<div>
<c:import url="jstl_import_b.jsp"/>
</div>
외부페이지<br/>
내부 페이지 변수의 사용 : ${ week }
<!-- jstl_import_b.jsp -->
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"
info="지시자의 충돌이 발생하지 않는다."%>
공통 디자인 정의<br/>
<%
String week = "오늘은 월요일입니다.";
pageContext.setAttribute("week", week);
%>
<strong>${ week }</strong>
- catch 사용 예 * scriptlet내용은 Java파일로 변경되면서 _jspService안에 코드로 생성된다. * scrriptlet안에 내용은 try~catch문으로 감싸여 있어서 코드 에러가 발생하지 않는다. * 반면에 declaration으로 메소드를 생성 후 에러코드를 넣으면 예외처리를 안했기 때문에 에러가 발생한다.
<!-- jstl_catch.jsp -->
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
...
<c:catch var="e">
<%
Class.forName("kk");
// 예외를 throws하는 method를 호출하더라도 _jspService()가
// 코드를 try~catch로 감싸고 있기 때문에 코드 에러가 발생하지 않는다.
%>
</c:catch>
<c:if test="${ not empty e }">
서비스도중 문제가 발생하였습니다. ${ e }
</c:if>
JSTL - fmt
- 형식을 가진 문자열을 만드는 Tag Library
- 지시자
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"/>
- 날짜형식 변경
- 브라우저로 출력된다.
- fmt - prefix/ formatDate - suffix
<fmt:formatDate value="date 객체" pattern="SimpleDateFormat Pattern과 동일"/>
- 숫자형식 변경
<fmt:formatNumber value="숫자" pattern="DecimalFormat Pattern과 동일" />
<!--
pattern값은 DecimalFormat에서 제공하는 pattern을 사용
데이터가 없을 때 0을 출력하고자 한다면 '0'(오라클은 0)
데이터가 없을 때 출력하지 않고자 한다면 '#'(오라클은 9)
-->
- fmt formatDate 사용 예
<!-- jstl_formatdate.jsp -->
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
...
<%
Date date = new Date();
pageContext.setAttribute("pageDate", date);
%>
<!-- EL 사용 -->
<fmt:formatDate value="${ pageDate }" pattern="yyyyMMdd EEEE HH:mm:ss"/><br/>
<!-- Expression 사용 -->
<fmt:formatDate value="<%= date %>" pattern="yyyyMMdd EEEE HH:mm:ss"/><br/>
- fmt formatNumber 사용 예
<!-- jstl_formatnumber.jsp -->
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
...
<%
int i = 20190318;
int j = 2019;
pageContext.setAttribute("i", i);
pageContext.setAttribute("j", j);
%>
<fmt:formatNumber value="${ i }" pattern="#,###"/><br/>
<fmt:formatNumber value="${ j }" pattern="#,###"/><br/>
- #은 있는 데이터만 출력, 0은 없는 데이터도 출력
<fmt:formatNumber value="${ i }" pattern="###,###,###,###"/><br/>
<fmt:formatNumber value="${ j }" pattern="0,000,000,000"/><br/>
달력 만들기
- 지금까지 배운 내용 총정리
- 다음 캘린더와 비슷하게 만들 것
- 초기 디자인
<!-- diary.jsp -->
...
<style type="text/css">
#diaryTab { margin: 0px auto; border-spacing: 0px; }
.sunTitle {
width: 80px;
height:25px;
border:1px solid #CECECE;
background-color: #e84118;
font-weight:bold;
color: white;
}
.weekTitle { width: 80px; height:25px; border:1px solid #CECECE; }
.satTitle {
width: 80px;
height:25px;
border:1px solid #CECECE;
background-color: #273c75;
color: white;
}
#diaryTitle {
text-align: center;
margin-bottom: 10px;
margin-tom: 20px;
}
#diaryToday {
width:100px;
font-family: 고딕체;
font-size:24px;
font-weight: bold;
vertical-align: bottom;
}
.diaryTd {
width: 100px;
height: 60px;
border: 1px solid #CECECE;
text-align: right;
vertical-align: top;
font-size: 14px;
font-weight: bold;
padding-right: 10px;
}
/* jQuery tooltip 플러그인 */
.ui-tooltip, .arrow:after {
background: black;
border: 2px solid white;
}
.ui-tooltip {
padding: 10px 20px;
color: white;
border-radius: 20px;
font: bold 14px "Helvetica Neue", Sans-Serif;
text-transform: uppercase;
box-shadow: 0 0 7px black;
}
.arrow {
width: 70px;
height: 16px;
overflow: hidden;
position: absolute;
left: 50%;
margin-left: -35px;
bottom: -16px;
}
.arrow.top {
top: -16px;
bottom: auto;
}
.arrow.left {
left: 20%;
}
.arrow:after {
content: "";
position: absolute;
left: 20px;
top: -20px;
width: 25px;
height: 25px;
box-shadow: 6px 5px 9px -9px black;
-webkit-transform: rotate(45deg);
-ms-transform: rotate(45deg);
transform: rotate(45deg);
}
.arrow.top:after {
bottom: -20px;
top: auto;
}
</style>
<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<script>
$( function() {
$( document ).tooltip({
position: {
my: "center bottom-20",
at: "center top",
using: function( position, feedback ) {
$( this ).css( position );
$( "<div>" )
.addClass( "arrow" )
.addClass( feedback.vertical )
.addClass( feedback.horizontal )
.appendTo( this );
}
}
});
} );
</script>
...
<div id="diaryWrap">
<%
Calendar cal = Calendar.getInstance();
int nowYear = cal.get(Calendar.YEAR);
int nowMonth = cal.get(Calendar.MONTH)+1;
int nowDay = cal.get(Calendar.DAY_OF_MONTH);
pageContext.setAttribute("nowYear", nowYear);
pageContext.setAttribute("nowMonth", nowMonth);
pageContext.setAttribute("nowDay", nowDay);
%>
<div id="diaryTitle">
<img src="images/btn_prev.png" title="이전 월"/>
<span id="diaryToday" title="${ nowYear }년 ${ nowMonth }월">
<c:out value="${ nowYear }"/>
.<c:out value="${ nowMonth }"/></span>
<img src="images/btn_next.png" title="다음 월"/>
<img src="images/btn_today.png" title="오늘"/>
</div>
<div id="diaryContent">
<table id="diaryTab">
<tr>
<th class="sunTitle">일</th>
<th class="weekTitle">월</th>
<th class="weekTitle">화</th>
<th class="weekTitle">수</th>
<th class="weekTitle">목</th>
<th class="weekTitle">금</th>
<th class="satTitle">토</th>
</tr>
<tr>
<td class="diaryTd">1</td>
</tr>
</table>
</div>
</div>
- Calendar 사용
- Singleton 패턴, 인스턴스 얻어 사용
Calendar cal = Calendar.getInstance();
- 마지막 날 얻기
- 이번 달에 없는 날짜가 입력되면 달력은 다음달 1일로 설정이 된다.
for(int i=1; ; i++) {
cal.set(Calendar.DAY_OF_MONTH, i);
if(cal.get(Calendar.DAY_OF_MONTH) != i) {
// 예로 32일이 들어가면 없는 날이라 첫날(1)로 돌아감, 반복문을 빠져나온다.
break;
}
// 마지막 날을 저장해서 사용
}
- 현재 날짜랑 tempDay랑 같지 않음으로 마지막 날을 알 수 있다
<tr>
<%
for(int tempDay = 1; tempDay < 33;tempDay++) {
cal.set(Calendar.DAY_OF_MONTH, tempDay);
out.println(cal.get(Calendar.DAY_OF_MONTH)+"/"+tempDay+"<br/>");
}
%>
</tr>
- 토요일이면 줄을 바꿔줘야 한다.
- 딱 떨어지는 수를 비교할 땐 switch case문을 사용
switch (cal.get(Calendar.DAY_OF_WEEK)) {
case Calendar.SATURDAY :
out.println("</tr><tr>");
}
<div id="diaryContent">
<table id="diaryTab">
<tr>
<th class="sunTitle">일</th>
<th class="weekTitle">월</th>
<th class="weekTitle">화</th>
<th class="weekTitle">수</th>
<th class="weekTitle">목</th>
<th class="weekTitle">금</th>
<th class="satTitle">토</th>
</tr>
<tr>
<%
// 매월마다 끝나는 날짜가 다르기 때문에
for(int tempDay = 1; ;tempDay++) {
// 임시일자를 설정
cal.set(Calendar.DAY_OF_MONTH, tempDay);
if (cal.get(Calendar.DAY_OF_MONTH) != tempDay) {
// 설정된 날짜가 현재일자가 아니라면 다음 달 1일
// 다음달이면 반복문을 빠져나간다.
break;
}
%>
<td class="diaryTd"><%= tempDay %></td>
<%
switch (cal.get(Calendar.DAY_OF_WEEK)) {
case Calendar.SATURDAY :
out.println("</tr><tr>");
}
}
%>
</tr>
</table>
</div>
- 1일을 출력하기 전, 시작요일에 맞게 공백을 출력해야 함. * 1일인 경우에 공백을 넣어주는 코드를 앞에 넣어줘야 함 * 월마다 찍는 공백의 수는 다름
// for 반복문 안에서
if ( i == 1 ) { // 이번에 찍을 날짜가 1일이라면 1일의 요일까지 공백넣기
for(int j=1; j < cal.get(Calendar.DAY_OF_WEEK); j++) {
out.println("<td></td>");
}
}
...
<%! // declaration, 필드에 상수 생성
public static final int START_DAY = 1;
%>
...
<tr>
<%
for(int tempDay = 1; ;tempDay++) {
cal.set(Calendar.DAY_OF_MONTH, tempDay);
if (cal.get(Calendar.DAY_OF_MONTH) != tempDay) {
break;
}
// 1일을 출력하기 전 1일 요일까지 공백 출력
switch(tempDay) {
case START_DAY:
for(int blankTd=1; blankTd < cal.get(Calendar.DAY_OF_WEEK); blankTd++) {
out.println("<td class='diaryTd'></td>");
}
}
%>
<td class="diaryTd"><%= tempDay %></td>
<%
switch (cal.get(Calendar.DAY_OF_WEEK)) {
case Calendar.SATURDAY :
out.println("</tr><tr>");
}
}
%>
</tr>
</table>
- 마지막날 이후에 공백 만들기 * 빠져나가기 전에 만들어야 한다.
if(cal.get(Calendar.DAY_OF_MONTH) != tempDay) { // 마지막 날짜를 넘고서
for(int j=cal.get(Calendar.DAY_OF_WEEK); j<8; j++) { // SAT(7)까지 공백넣기
out.println("<td></td>");
}
break;
}
...
<tr>
<%
// 매월마다 끝나는 날짜가 다르기 때문에
for(int tempDay = 1; ;tempDay++) {
cal.set(Calendar.DAY_OF_MONTH, tempDay); // 임시일자를 설정
if (cal.get(Calendar.DAY_OF_MONTH) != tempDay) {
// 설정된 날짜가 현재일자가 아니라면 마지막 일자 다음달 1일이므로 반복문을 빠져나간다.
for(int blankTd=cal.get(Calendar.DAY_OF_WEEK); blankTd<8; blankTd++) {
out.println("<td class='blankTd'></td>");
}
break;
}
// 1일을 출력하기 전 공백 출력
switch(tempDay) {
case START_DAY:
for(int blankTd=1; blankTd<cal.get(Calendar.DAY_OF_WEEK); blankTd++) {
out.println("<td class='blankTd'></td>");
}
}
%>
<td class="diaryTd"><%= tempDay %></td>
<%
switch (cal.get(Calendar.DAY_OF_WEEK)) {
case Calendar.SATURDAY :
out.println("</tr><tr>");
}
}
%>
</tr>
</table>
- 토요일, 일요일 글자색을 바꾸기
...
.sunColor {
color: #e84118;
font-size:15px;
}
.satColor {
color: #273c75;
}
.weekColor {
color: #333;
}
</style>
...
String dayClass="";
...
// 요일별 색 설정
switch(cal.get(Calendar.DAY_OF_WEEK)) {
case Calendar.SUNDAY:
dayClass="sunColor";
break;
case Calendar.SATURDAY:
dayClass="satColor";
break;
default:
dayClass="weekColor";
}
pageContext.setAttribute("dayClass", dayClass);
%>
<td class="diaryTd"><span class="${ dayClass }"><%= tempDay %></span></td>
<%
...
- a태그 href 속성값 #void와 #의 차이 * #void는 눌러도 제자리(아무반응없이) * #은 페이지를 갱신해서 스크롤이 아래로 내려가 있었다면 페이지 위로 올라옴
- 이전, 다음 달로 이동
- 쿼리스트링에 파라미터로 이동할 달 페이지 요청
<a href="diary.jsp?param_month=${ nowMonth-1 }"><img src="images/btn_prev.png" title="이전 월"/></a>
<%
Calendar cal = Calendar.getInstance();
int nowYear = cal.get(Calendar.YEAR);
int nowMonth = 0;
int nowDay = cal.get(Calendar.DAY_OF_MONTH);
String paramMon = request.getParameter("param_month");
if (paramMon != null) { // 파라미터 월이 존재한다면 현재 켈린더 객체의 월을 변경
cal.set(Calendar.MONTH, Integer.parseInt(paramMon)-1);
}
nowMonth = cal.get(Calendar.MONTH)+1;
pageContext.setAttribute("nowYear", nowYear);
pageContext.setAttribute("nowMonth", nowMonth);
pageContext.setAttribute("nowDay", nowDay);
%>
- 문제는 2019년으로만 유지됨 * parameter로 ‘년’도 추가 * 1월일 때 12월 이동할 때 상태바로 파라미터가 제대로 전달되는지 확인
int nowYear = 0;
...
String paramYear = request.getParameter("param_year");
if (paramYear != null) {
cal.set(Calendar.YEAR, Integer.parseInt(paramYear));
}
nowYear = cal.get(Calendar.YEAR);
...
<a href="diary.jsp?param_month=${ nowMonth-1 eq 0 ? 12 : nowMonth-1 }¶m_year=${ nowMonth-1 eq 0 ? nowYear-1 : nowYear }"><img src="images/btn_prev.png" title="이전 월"/></a>
- 이번엔 JavaScript로 월 이동 구현
<script type="text/javascript">
function moveMonth(month, year) { // JS로 페이지 이동
location.href="diary.jsp?param_month="+month+"¶m_yaer="+year;
}
</script>
...
<a onclick="moveMonth(${ nowMonth-1 eq 0 ? 12 : nowMonth-1 },${ nowMonth-1 eq 0 ? nowYear-1 : nowYear })"><img src="images/btn_prev.png" title="이전 월"/></a>
- 폼으로 넘기기
- hidden태그로 post방식으로 전달
<script type="text/javascript">
function moveMonth(month, year) {
var obj = document.diaryFrm;
obj.param_month.value = month;
obj.param_year.value = year;
obj.submit();
}
</script>
...
<form action="diary.jsp" name="diaryFrm" method="post">
<input type="hidden" name="param_month"/>
<input type="hidden" name="param_year"/>
</form>
...
<a href="#void" onclick="moveMonth(${ nowMonth == 13 ? 1 : nowMonth+1 },${ nowMonth == 13 ? nowYear+1 : nowYear })"><img src="images/btn_next.png" title="다음 월"/></a>
- 위 함수를 jQuery로 바꾸면
<script type="text/javascript">
function moveMonth(month,year) {
$("[name='param_year']").val(year);
$("[name='param_month']").val(month);
$("[name='diaryFrm']").submit();
}
</script>
- 오늘로 이동
- jsp 요청 시 파라미터 없으면 오늘로 이동한다.
<a href="diary.jsp"><img src="images/btn_today.png" title="오늘"/></a>
- 오늘로 이동 - JS함수로 이동
- 함수에 empty값 전달해서 이동 시 empty일 때 예외처리를 해줘야 에러가 없다
<a href="#void" onclick="moveMonth('','')"><img src="images/btn_today.png" title="오늘"/></a>
...
String paramMon = request.getParameter("param_month");
if (paramMon != null && !"".equals(paramMon)) { // "" empty 조건 추가
cal.set(Calendar.MONTH, Integer.parseInt(paramMon)-1);
}
String paramYear = request.getParameter("param_year");
if (paramYear != null && !"".equals(paramYear)) { // "" empty 조건 추가
cal.set(Calendar.YEAR, Integer.parseInt(paramYear));
}
...
- 숙제1 - 오늘을 표현하기(색상)
Calendar cal = Calendar.getInstance();
Calendar todayCal = Calendar.getInstance();
int currYear = todayCal.get(Calendar.YEAR);
int currMon = todayCal.get(Calendar.MONTH);
int currDay = todayCal.get(Calendar.DAY_OF_MONTH);
...
if(currYear == cal.get(Calendar.YEAR) && currMon == cal.get(Calendar.MONTH) && currDay == tempDay) {
todayClass = "today";
} else {
todayClass = "";
}
pageContext.setAttribute("todayClass", todayClass);
<td class="diaryTd ${ todayClass }"><span class="${ dayClass }"><%= tempDay %></span></td>
- 숙제2 - 마지막 날이 토요일이면 다음 줄의 공백생성을 막아라
int lastDay=0;
...
for(int tempDay = 1; ;tempDay++) {
cal.set(Calendar.DAY_OF_MONTH, tempDay);
if (cal.get(Calendar.DAY_OF_MONTH) != tempDay) {
for(int blankTd=cal.get(Calendar.DAY_OF_WEEK); blankTd<8; blankTd++) {
if (lastDay == Calendar.SATURDAY) {
// 마지막날이 토요일이면 빠져나옴
break;
}
out.println("<td class='blankTd'></td>");
}
break;
}
...
// 마지막날 저장
lastDay = cal.get(Calendar.DAY_OF_WEEK);
}
%>
- 숙제3 - 마지막 날 이후에 다음달의 일자를 1, 2, 3, 4 일 등 공백에 출력
...
if (cal.get(Calendar.DAY_OF_MONTH) != tempDay) {
int nextCnt = 1; // 다음달 시작 날짜
for(int blankTd=cal.get(Calendar.DAY_OF_WEEK); blankTd<8; blankTd++) {
if (lastDay == Calendar.SATURDAY) {
break;
}
out.print("<td class='blankTd'>");
out.print(nextCnt); // 시작날짜를 찍고
out.println("</td>");
nextCnt++; // 증가시킴
}
break;
}
...
- 숙제4 - 1일 이전에 이전 달의 마지막 일자부터 출력.
// 1일을 출력하기 전 공백 출력
switch(tempDay) {
case START_DAY:
Calendar prevCal = Calendar.getInstance(); // 지난 달을 저장할 Calendar 객체
prevCal.set(Calendar.MONTH, cal.get(Calendar.MONTH)-1); // 지난달로 설정
int prevLastDay = 0;
for(int temp = 1; ;temp++) { // 지난 달 마지막 날까지 반복
prevCal.set(Calendar.DAY_OF_MONTH, temp);
if(prevCal.get(Calendar.DAY_OF_MONTH) != temp) { // 다음달로 바뀔 때까지
break;
}
prevLastDay = prevCal.get(Calendar.DATE); // 마지막 날짜 저장
}
int cnt = -1;
for(int blankTd=1; blankTd<cal.get(Calendar.DAY_OF_WEEK); blankTd++) {
cnt++; // 공백의 수를 카운트
}
for(int blankTd=1; blankTd<cal.get(Calendar.DAY_OF_WEEK); blankTd++) {
out.print("<td class='blankTd'>");
out.print(prevLastDay - cnt); // 마지막날짜 - 공백 수
out.println("</td>");
cnt--;
}
}