[회사일] DateRange 추가

오른쪽에 보이는 검색 필드를 MemberSearchingParam에 추가하는데 아무래도 이런 범위 검색 필드는 자주 사용될 것 같더군요.
그래서 별도의 클래스로 분리했습니다.
public class DateRange {
    @DateTimeFormat(iso= DateTimeFormat.ISO.DATE)
    private Date from;
    @DateTimeFormat(iso= DateTimeFormat.ISO.DATE)
    private Date to;
}
나머진 요다(?) 조다(?) 라이브러리만 넣어두면 기본 포매터가 동작해서 잘 바인딩 해 줍니다. 아.. 암튼 이번엔 바인딩 얘기가 아니라..  저 클래스 DateRange 얘기입니다.
MemberSearchParam을 MemberDao코드에 적용합니다.
@Repository
public class MemberDaoImpl extends GenericDaoImpl<Member, MemberSearchParam> implements MemberDao {
    protected void applySearchParam(Criteria c, MemberSearchParam searchParam) {
        CriteriaUtils.addOptionalLike(c, “loginId”, searchParam.getLoginId());
        CriteriaUtils.addOptionalLike(c, “name”, searchParam.getName());
        CriteriaUtils.addOptionalLike(c, “email”, searchParam.getEmail());
        CriteriaUtils.addOptionalLike(c, “phoneNumber”, searchParam.getPhoneNumber());
        CriteriaUtils.addOptionalLike(c, “jobTiTle”, searchParam.getJobTitle());
        CriteriaUtils.addOptionaBetween(c, “birthDay”, searchParam.getBirthDayRange());
    }
}
이게 DAO 코드 전부입니다.
유틸 코드로 가야겠네요.
 
   @Test
    public void testOptionalBetween() {
        String dateRangeSearchingFiled = “birthDay”;
        CriteriaUtils.addOptionaBetween(c, dateRangeSearchingFiled, null);
        verify(c, times(0)).add(any(Criterion.class));
        reset(c);
        DateRange dateRange = new DateRange();
        CriteriaUtils.addOptionaBetween(c, dateRangeSearchingFiled, dateRange);
        verify(c, times(0)).add(any(Criterion.class));
        reset(c);
        dateRange.setFrom(new Date());
        CriteriaUtils.addOptionaBetween(c, dateRangeSearchingFiled, dateRange);
        verify(c, times(1)).add(Restrictions.ge(any(String.class), dateRange.getFrom()));
        reset(c);
        dateRange.setTo(new Date());
        CriteriaUtils.addOptionaBetween(c, dateRangeSearchingFiled, dateRange);
        verify(c, times(1)).add(Restrictions.between(any(String.class), dateRange.getFrom(), dateRange.getTo()));
        reset(c);
        dateRange.setFrom(null);
        CriteriaUtils.addOptionaBetween(c, dateRangeSearchingFiled, dateRange);
        verify(c, times(1)).add(Restrictions.le(any(String.class), dateRange.getTo()));
    }
장황해 보이지만 별거 없는 테스트 입니다.
  
 public static void addOptionaBetween(Criteria c, String fieldName, DateRange dateRange) {
        if(dateRange == null)
            return;
        if(dateRange.getFrom() != null && dateRange.getTo() != null){
            c.add(Restrictions.between(fieldName, dateRange.getFrom(), dateRange.getTo()));
        }
        if(dateRange.getFrom() != null && dateRange.getTo() == null){
            c.add(Restrictions.ge(fieldName, dateRange.getFrom()));
        }
        if(dateRange.getFrom() == null && dateRange.getTo() != null){
            c.add(Restrictions.le(fieldName, dateRange.getTo()));
        }
    }
이 코드를 테스트하고 있죠. 그런데 코드가 좀;; Don’t Tell, Ask가 생각나더군요. 저런 조건 문을 inline 뭐시기 리팩토링으로 분리할 수도 있지만 그보다 일단 if 조건문에 들어있는 내용이 들어있어야 할 위치가… DateRange가 되어야 할 것 같단 생각이 듭니다.
머 일단 if안에 있는 조건문 들을 extract method 리팩토링으로 빼냅니다. 이런 리팩토링을 인라인 머시기 리팩토링이라고 했던것 같은데 잊어버렸네요. @_@;;
 
   public static void addOptionaBetween(Criteria c, String fieldName, DateRange dateRange) {
        if(dateRange == null)
            return;
        if(hasFromAndTo(dateRange)){
            c.add(Restrictions.between(fieldName, dateRange.getFrom(), dateRange.getTo()));
        }
        if(hasFromOnly(dateRange)){
            c.add(Restrictions.ge(fieldName, dateRange.getFrom()));
        }
        if(hasToOnly(dateRange)){
            c.add(Restrictions.le(fieldName, dateRange.getTo()));
        }
    }
    private static boolean hasToOnly(DateRange dateRange) {
        return dateRange.getFrom() == null && dateRange.getTo() != null;
    }
    private static boolean hasFromOnly(DateRange dateRange) {
        return dateRange.getFrom() != null && dateRange.getTo() == null;
    }
    private static boolean hasFromAndTo(DateRange dateRange) {
        return dateRange.getFrom() != null && dateRange.getTo() != null;
    }
그럼 이렇게 되는데 여기서 아래 세개의 static 메서드를 DateRange 쪽 멤버 메서드로 옯겨줍니다.
public class DateRange {
    @DateTimeFormat(iso= DateTimeFormat.ISO.DATE)
    private Date from;
    @DateTimeFormat(iso= DateTimeFormat.ISO.DATE)
    private Date to;
    public boolean hasToOnly(DateRange dateRange) {
        return dateRange.getFrom() == null && dateRange.getTo() != null;
    }
    public boolean hasFromOnly(DateRange dateRange) {
        return dateRange.getFrom() != null && dateRange.getTo() == null;
    }
    public boolean hasFromAndTo(DateRange dateRange) {
        return dateRange.getFrom() != null && dateRange.getTo() != null;
    }
}
그리고 CriteriaUtils 코드를 수정해주면 되죠.
  
 public static void addOptionaBetween(Criteria c, String fieldName, DateRange dateRange) {
        if(dateRange == null)
            return;
        if(dateRange.hasFromAndTo(dateRange)){
            c.add(Restrictions.between(fieldName, dateRange.getFrom(), dateRange.getTo()));
        }
        if(dateRange.hasFromOnly(dateRange)){
            c.add(Restrictions.ge(fieldName, dateRange.getFrom()));
        }
        if(dateRange.hasToOnly(dateRange)){
            c.add(Restrictions.le(fieldName, dateRange.getTo()));
        }
    }
마지막으로 테스트 한번 돌려주면 깔끔하게 끝~

[회사일] view.jsp 태그파일 적용

member/view.jsp
<page:viewpage label=”계정 정보”>
    <v:text label=”id” value=”${model.id}”/>
    <v:text label=”로그인ID” value=”${model.loginId}”/>
    <v:text label=”이름” value=”${model.name}”/>
    <v:text label=”이메일” value=”${model.email}”/>
</page:viewpage>
code/view.jsp
<page:viewpage label=”코드 정보”>
    <v:text label=”id” value=”${model.id}” />
    <v:text label=”코드종류” value=”${model.cate.descr}” />
    <v:text label=”코드값” value=”${model.code}” />
    <v:text label=”코드명” value=”${model.name}” />
    <v:text label=”설명” value=”${model.descr}” />
</page:viewpage>
흠냐.. 뷰용 태그 파일 네임스페이스는 v로 정했습니다.
자 거럼 이제 찍어낼 준비가 거의 다 된 것 같군요… 흠.. 아니군요. 아직 갈길이 멀군요.
일단 Member에 Date 타입 필드 부터 추가해봐야겠습니다.
다음에는 Right 도메인을 찍어내고, Member랑 다대다 관계 연결해서 뷰까지 또 프레임워크성 코드를 빼내야 하고…
그담엔 시큐리티 적용하고..
그담엔 자동완성 만들고.. 
엑셀 다운/업로드 만들고..
멀구나 멀어..

[회사일] new.jsp 태그파일 적용

code/new.jsp
<page:newpage label=”새 코드”>
    <f:select label=”코드종류” path=”cate” items=”${ref.codeCateList}” />
    <f:input label=”코드값” path=”code” />
    <f:input label=”코드명” path=”name” />
    <f:textarea label=”설명” path=”descr” />
</page:newpage>
member/new.jsp
<page:newpage label=”새 계정”>
    <f:input label=”로그인 ID” path=”loginId”/>
    <f:input label=”비밀번호” path=”password”/>
    <f:input label=”이름” path=”name”/>
    <f:input label=”이메일” path=”email”/>
</page:newpage>
new.jsp 페이지는 edit.jsp랑 비슷했으니까 간단간단 이제 view.jsp 페이지만 남았군요.

[회사일] mgt.jsp 파일에 태그 파일 적용

가운데는 그리드가 와야하고 오른쪽에는 검색 화면이 나와야하는 페이지. 이 페이지에 태그 파일을 적용해서…
<page:mgtpage label=”계정 관리”>
    <page:search label=”계정 검색”>
        <s:input label=”이름” path=”name” />
        <s:input label=”이메일” path=”email” />
    </page:search>
    <script type=”text/javascript”>
        $(function() {
            $(“#smdis-grid”).jqGrid({
                colNames:[‘id’, ‘아이디’, ‘이름’, ‘이메일’],
                colModel :[
                    {name:’id’, index:’id’, width:55, hidden:true},
                    {name:’loginId’, index:’loginId’, width:80},
                    {name:’name’, index:’name’, width:90},
                    {name:’email’, index:’email’, width:90},
                ]
            });
        });
    </script>
</page:mgtpage>
이런식으로 만들었습니다. 딱 봐도.. 검색 부분에 머머가 있구나.. 그리드에는 머머가 있구나가 보이니깐 이정도면 만족합니다. 태그 파일을 계속 써보니까 그 장점 중 하나가 태그에 좀 더 의미있는 이름을 줄 수 있다는 것이 있더라구요. 대신 단점으로는 이 태그 파일만 가지고 테스트 할 수 없다는게 좀 안타깝습니다. 계속해서 서버를 띄워둔 상태에서 릴로딩 해가면서 확인하고 있는데.. 아마 프리마커 같은 템플릿 엔진의 장점은 바로 테스트 용이성이 아닐까 싶습니다.
머 어쨋든 지금은 일단 JSP + 태그파일로 후딱 만들기로 했습니다. 
나중에 프리마커 + 매크로를 사용해서 대체해보는 방법도 생각해볼 수 있겠지만.. 머 나중에;
태그 파일을 분류해서 page는 페이지 구조와 관련된 태그를 만들고..
s는 검색에 사용할 폼 태그들을 만들고..
f는 수정이나 추가할 때 사용할 폼 태그들을 만들기로 했습니다.
 

[회사일] edit.jsp 화면에 태그 파일 적용하기

먼저 Code 도메인의 edit.jsp 코드를 보겠습니다.

<%@ page language=”java” contentType=”text/html; charset=UTF-8″ pageEncoding=”UTF-8″ %>
<%@ taglib prefix=”page” tagdir=”/WEB-INF/tags/page” %>
<%@ taglib prefix=”form” uri=”http://www.springframework.org/tags/form” %>
<page:page>
    <page:head/>
    <page:body>
        <div class=”ui-layout-center”>
            <h2 class=”smdis-pane-title” id=”smdis-grid-title”>
                <ul class=”smdis-left-icons”>
                    <li id=”smdis-save-button” class=”ui-state-default ui-corner-all” title=”저장”><span class=”ui-icon ui-icon-disk”></span></li>
                    <li id=”smdis-delete-button” class=”ui-state-default ui-corner-all” title=”삭제”><span class=”ui-icon ui-icon-trash”></span></li>
                    <li id=”smdis-reload-button” class=”ui-state-default ui-corner-all” title=”원래값으로”><span class=”ui-icon ui-icon-refresh”></span></li>
                </ul>
                <ul class=”smdis-right-icons”>
                    <li id=”smdis-list-button” class=”ui-state-default ui-corner-all” title=”목록으로”><span class=”ui-icon ui-icon-search”></span></li>
                    <li id=”smdis-back-button” class=”ui-state-default ui-corner-all” title=”뒤로”><span class=”ui-icon ui-icon-arrowreturnthick-1-w”></span></li>
                </ul>
                코드 수정
            </h2>
            <form:form commandName=”model” action=”/base/code/${model.id}” method=”PUT”>
                <p class=”ui-widget-content”><label>코드종류</label><form:select path=”cate” items=”${ref.codeCateList}” itemValue=”value” itemLabel=”descr” /><form:errors path=”cate” cssClass=”smdis-error-message”/></p>
                <p class=”ui-widget-content”><label>코드값</label><form:input path=”code”/><form:errors path=”code” cssClass=”smdis-error-message”/></p>
                <p class=”ui-widget-content”><label>코드명</label><form:input path=”name”/><form:errors path=”name” cssClass=”smdis-error-message”/></p>
                <p class=”ui-widget-content”><label>설명</label><form:textarea path=”descr”/></p>
            </form:form>
        </div>
        <%–//검색/정렬–%>
        <div class=”ui-layout-east”>
            <h2 class=”smdis-pane-title”>
                <ul class=”smdis-left-icons”>
                    <li id=”smdis-grid-apply” class=”ui-state-default ui-corner-all” title=”적용”><span class=”ui-icon ui-icon-arrowthickstop-1-w”></span></li>
                </ul>
                <ul class=”smdis-right-icons”>
                    <li class=”ui-state-default ui-corner-all” title=”적용하고 닫기”><span class=”ui-icon ui-icon-arrowthickstop-1-e”></span></li>
                </ul>
                검색
            </h2>
        </div>
    <script type=”text/javascript”>
        $(function() {
            $(“button”).button();
            $(“#smdis-save-button”).click(function(){
                var result = confirm(“저장 하시겠습니까?”);
                if(!result) return;
                $(“form input[name=’_method’]”).attr(“value”, “PUT”);
                $(“form”).submit();
            });
            $(“#smdis-delete-button”).click(function(){
                var result = confirm(“삭제 하시겠습니까?”);
                if(!result) return;
                
                $(“form input[name=’_method’]”).attr(“value”, “DELETE”);
                $(“form”).submit();
            });
            $(“#smdis-reload-button”).click(function(){
                var result = confirm(“변경 사항을 취소 하시겠습니까?”);
                if(!result) return;
                $(document).attr(“location”, ‘/base/code/${model.id}/form’);
            });
            $(“#smdis-back-button”).click(function(){
                $(document).attr(“location”, ‘/base/code/${model.id}’);
            });
            $(“#smdis-list-button”).click(function(){
                $(document).attr(“location”, ‘/base/code/mgt’);    
            });
        });
    </script>
    </page:body>
</page:page>
일부는 page라는 태그파일을 적용해서 코드를 많이 줄여놨습니다. 그래도 너무 길고.. Member의 edit.jsp 파일을 만들 때 손대는 곳은 굵은 글씨 밖에 없습니다. 신경쓸 곳만 신경쓰게 만들고 나머진 신경쓰고 싶지도 않고 보고 싶지도 않습니다. 그리고 위와 같은 코드가 여러 JSP에 중복되면.. 결국 자바 중복 코드랑 다를께 없습니다. 화면 코드에서도 중복을 제거해야 합니다.
Member 도메인의 edit.jsp 파일은 결국 다음과 같이 바꼈습니다.
<%@ page language=”java” contentType=”text/html; charset=UTF-8″ pageEncoding=”UTF-8″ %>
<%@ taglib prefix=”page” tagdir=”/WEB-INF/tags/page” %>
<%@ taglib prefix=”osaf” tagdir=”/WEB-INF/tags/osaf” %>
<%@ taglib prefix=”form” uri=”http://www.springframework.org/tags/form” %>
<page:page>
    <page:head/>
    <page:body>
        <page:edit label=”계정 수정”>
            <osaf:input label=”로그인ID” path=”loginId”/>
            <osaf:input label=”비밀번호” path=”password”/>
            <osaf:input label=”이름” path=”name”/>
            <osaf:input label=”이메일” path=”email”/>
        </page:edit>
        <page:searchWithoutButtons />
    </page:body>
</page:page>
정말 이게 전부입니다. 잡습 코드도 잡습이 적용될 코드가 들어있는 태그 파일로 넣어놨습니다. Code의 edit.jsp 파일에도 태그 파일을 적용하면..
<%@ page language=”java” contentType=”text/html; charset=UTF-8″ pageEncoding=”UTF-8″ %>
<%@ taglib prefix=”page” tagdir=”/WEB-INF/tags/page” %>
<%@ taglib prefix=”osaf” tagdir=”/WEB-INF/tags/osaf” %>
<%@ taglib prefix=”form” uri=”http://www.springframework.org/tags/form” %>
<page:page>
    <page:head/>
    <page:body>
        <page:edit label=”코드 수정”>
            <osaf:select label=”코드종류” path=”cate” items=”${ref.codeCateList}” itemValue=”value” itemLabel=”descr” />
            <osaf:input label=”코드값” path=”code”/>
            <osaf:input label=”코드명” path=”name”/>
            <osaf:textarea label=”설명” path=”descr”/>
        </page:edit>
        <page:searchWithoutButtons />
    </page:body>
</page:page>
이렇게 됩니다. 사실 저기서 위아래 세줄 정도도 관심 대상이 아니라서 page:editpage 같은 태그 파일을 만들어 줄일 수도 있습니다.
<%@ page language=”java” contentType=”text/html; charset=UTF-8″ pageEncoding=”UTF-8″ %>
<%@ taglib prefix=”page” tagdir=”/WEB-INF/tags/page” %>
<%@ taglib prefix=”osaf” tagdir=”/WEB-INF/tags/osaf” %>
<%@ taglib prefix=”form” uri=”http://www.springframework.org/tags/form” %>
<page:editpage label=”코드 수정”>
    <osaf:select label=”코드종류” path=”cate” items=”${ref.codeCateList}” itemValue=”value” itemLabel=”descr” />
    <osaf:input label=”코드값” path=”code”/>
    <osaf:input label=”코드명” path=”name”/>
    <osaf:textarea label=”설명” path=”descr”/>
</page:editpage>
좋아 좋아!!! 이제부터는 이렇게 딱 다섯줄만 코딩하면…
이런 화면을 만들 수 있는 겁니다.
이대로 끝내면 아쉬우니까 태그파일 코드 몇개만 공개하겠습니다.
WEB-INF/tags/page/editpage.tag
<%@ tag pageEncoding=”utf-8″ %>
<%@ taglib prefix=”page” tagdir=”/WEB-INF/tags/page” %>
<%@ taglib prefix=”form” uri=”http://www.springframework.org/tags/form” %>
<%@ attribute name=”label” required=”true”%>
<page:page>
    <page:head/>
    <page:body>
        <page:edit label=”${label}”>
            <jsp:doBody/>
        </page:edit>
        <page:searchWithoutButtons />
    </page:body>
</page:page>
WEB-INF/tags/osaf/select.tag
<%@ tag pageEncoding=”utf-8″ %>
<%@ taglib prefix=”page” tagdir=”/WEB-INF/tags/page” %>
<%@ taglib prefix=”form” uri=”http://www.springframework.org/tags/form” %>
<%@ attribute name=”label” required=”true”%>
<%@ attribute name=”path” required=”true”%>
<%@ attribute name=”items” type=”java.util.List” required=”true”%>
<%@ attribute name=”itemValue” required=”false”%>
<%@ attribute name=”itemLabel” required=”false”%>
<p class=”ui-widget-content”>
    <label>${label}</label>
    <form:select path=”${path}” items=”${items}” itemValue=”${itemValue}” itemLabel=”${itemLabel}” />
    <form:errors path=”${path}” cssClass=”smdis-error-message”/>
</p>