번역서가 나왔네. 오예…

http://www.yes24.com/24/goods/7039531

실시간 사용자 경험 프로그래밍이라는 이름으로 재밌는 책이 나왔습니다. 제가 번역했어요. 캬캬캬. 번역 실력이 안좋아서 편집자분께서 너무 많은 고생을 하셨지요. 한빛출판사의 한동훈님께 이자리를 빌어 깊은 감사와 미안한 마음을 표현하고 싶습니다. 책이 나오면 제가 밥을 쏘기로 했으니 꼭.. 밥한끼 하시죠.

이 책은 특징상 소스 코드가 많고 다양한 언어를 사용하고 있어서 매우 재밌습니다. 특히 마지막 장에서 세균전과 비슷한 게임을 만드는 내용이 있지요. 이 책에 나온 모든 내용을 총 망라하는 그런 게임이 되겠습니다.

부디 즐겁게 읽어주시고 부족한 부분이 있다면 역자인 저 말고 저자분께 메일을 보내 따져주시길 기대하지만… 저에게 보내셔도.. 딱히 제가.. 음… 암튼 즐겁게 보시어요. ㅋㅋㅋ

 

 

[Agile 번역] 어떻게 하면 번역을 기민하게 할 수 있을까?

번역은 쉽지 않다. 하지만 나름 재미는 있다. 돈은 되지 않지만, 하고 나면 뿌듯하다. 책으로 치면 지금하고 있는 책까지 두 권째다. 난 번역을 잘하지 못한다. 한글 실력이 엉망이고 책읽기를 별로 좋아하지 않아서 인지 문학 도서에 비하면 비교적 쉬운 영어를 사용하는 서적들 임에도 불구하고 한글 문장으로 옮기는 일이 쉽지 않다. 그래도 최소한 내가 다시 읽었을 때 이해할 수 있을 정도의 수준 만큼은 달성하고자 노력한다.

그렇게 어렵사리 번역을 하고나면 사실 다른 사람의 시각에서 읽었을 때 어떠한지 궁금하다. 그래서 베타리딩을 한다. 베타리딩을 하면서 내가 미처 감지하지 못했던 어색한 부분과 이해 못할 만한 부분이 드러난다. 그 부분을 개선하는 작업은 중요하다. 내가 읽었을 때 이해할 수 있었던 건 난 원문의 내용을 읽었기 때문이다. 그래서 원문을 보지 않고도 이해할 수 있는지 알려면 반드시 베타리딩이 필요하다. 코딩으로 치면 일종의 테스트다. 그런데 베타리딩도 쉽지 않다. 하이버네이트 같은 경우 한 챕터당 워드로 100페이지 정도가 된다. 넘는 것도 있고 덜 되는 것도 있지만 대충 그렇다. 그걸 바쁜 개발자들에게 읽어달라고 부탁하기가 참 뭐하다. 그 분들이 받는 댓가라고는 책을 미리 읽어볼 수 있다는 것 정도?
그래서 이런 저런 이유로 고민 중에 번역에도 Agile 기법을 도입하면 어떨까 하는 생각이 들었다. 그래서 상상을 해봤다.
페어 번역 
두 명이 앉아서 번역을 한다. 한명은 부르고 한명은 타이핑 하거나 그 반대로 한명이 읊으면서 동시에 타이핑도 하고 옆 사람은 보고 있다가 이상한 문장이나 틀린걸 봐주면 될 것 같다. 하지만 이건 번역으로 먹고 사는 사람이 아닌 이상 힘들어 보인다. 그렇게 30분 정도 번역을 한 다음 교대한다.
이런식으로 하루에 2시간을 번역한다고 쳤을 때 두 사람은 각각 1시간 번역 1시간 리뷰를 하게 된다. 한 사람이 2시간을 번역할 때와 2사람이 각각 1시간 번역/1시간 리뷰를 했을 때의 생산량과 품질을 확인해보고 싶다.
리뷰 퍼스트 번역
타이핑을 하기 전에 먼저 말로 리뷰를 한다. 아 이 문장은 대충 요러 요러한 이야기 같네요. 이렇게 옮기겠습니다. 라고 말을 한 뒤에 타이핑을 한다. 그리고 타이핑이 끝나면 옆 사람과 리팩토링을 진행한다. 어설프거나 한글 어법에 어긋나면 교정한다. 나중에 QA 팀 겪인 출판사의 검수자가 봐주겠지만 최소한의 품질을 보장하기 위한 수단이라고 보면 되겠다. 
페어 번역을 진행할 때 타이핑만 하지 않고 리뷰를 함으로써 대화를 유도할 수 있다. 하지만 대화가 목적은 아니기에 잡담으로 새는 일이 없게 주의해야겠다. 리뷰를 최소화하고 바로 타이핑을 한다. 그 다음 리팩토링에 해당하는 작업도 반드시 단락 단위나 문자 단위로 하는게 좋겠다. 이런 과정이 없다면 페어 번역을 하는 의미가 없으니 이렇게 하지 않을꺼면 페어 번역을 하지 않는게 좋겠다.
점진적인 베타리딩
베타리더에게 전달하는 과정을 일종의 배포로 간주하고 Agile 기법인 여러번 그리고 점진적으로 배포하는 수단을 번역에도 도입하면 어떨까. 예를 들어 베타리딩 주기를 1주일로 잡고 1주일치 번역한 내용을 베타리더에게 공개한다. 그리고 피드백을 받은 다음 그 주 초에 지난 배포의 개선 작업을 한 다음에 주 중~말까지 새로운 번역 작업을 진행하는 식이다.
베타리더에게 한번에 100 페이지씩 읽어야 하는 부담을 줄일 수 있고 피드백을 초기에 받음으로써 나중에 뭉탱이로 중복적인 피드백을 받는 것을 개선할 수 있을 것 같다. 하지만 베타리더도 번역자 못지않게 기민해야 할 것이다.
다음 번역은 봄싹에서 진행할 계획인데 이 세가지를 시도해 봐야겠다. 
과연.. 어떨까.. 후훗.. 재밌을 것 같다.

[봄싹] 스프링 레퍼런스 3.0 번역 시작

http://springsprout.org/wiki/464.do

3.0 레퍼런스 번역을 꾸준히 하겠습니다. 그동안 블로그에 조금씩 번역해서 올려두기도 했었는데 아무래도 레퍼런스 글은 블로그에서 찾아보는게 불편해서 봄싹 위키에 정리하기로 했습니다.

일단은 저 혼자 시작합니다. 하지만 봄싹 회원이라면 누구나 위키 페이지를 추가/수정/이동 시킬 수 있기 때문에 자유롭게 참여하실 수 있습니다.

정해진 틀도 없고 파트를 나누지도 않았지만, 봄싹 사이트도 처음에는 이런 방법으로 개발을 시작했습니다. 지금은 제법 틀도 갖춰져 있고, 특정 모듈 또는 기능 담당자(? 라기 보단 스스로 책임을 느끼시는 분들)도 있습니다. 수직 구조로 누가 누구에게 지시하거나 일을 나눠주지 않고 수평구조로 서로 토론하며 자신이 만들고 싶은 기능을 마음대로 구현해 넣고 있습니다. 레퍼런스도 이런 방법으로 번역을 완성할 겁니다.

기여(?).. 흠. 참여하고 싶으신 분들은 언제나 대환영입니다.

파이팅!

ps: 위키 수정/추가시 포인트를 계산해서 위키 기여도를 측정해야겠군요. 가장 많이 기여한 분에게 봄싹 티셔츠라도…

ps: 위키 미리 보기 화면과 실제 화면이 좀 다른데;; 아마도 조만간 소내기형이 수정해주지 않을까 싶네요… 형 수정해주세요.. ㅠ.ㅠ

SWF 12장 JSF 통합

12.1. 도입

스프링 Faces는 스프링의 JSF 통합 모듈로 스프링에서 JSF 사용을 간편하게 해준다. JSF UI 컴포넌트 모델을 스프링 MVC와 스프링 웹 플로우 컨틀로러와 함께 사용할 수 있게 해준다.

스프링 Faces는 또한 Ajax와 클리이언트쪽 검증 기능을 제공하는 자그마한 Facelets도 제공한다. 이 컴포넌트 라이브러리는 스프링 자바스크립트를 기반으로 만들었다. 스프링 자바스크립트는 Dojo를 기반 UI 툴킷으로 통합한 자바스크립트 추상화 프레임워크드다.

12.2. 스프링-중심 통합 방법

스프링 Faces는 JSF의 UI 컴포넌트 모델 장정을 스프링의 컨트롤러와 설정 모델 장점과 결합해준다. 아무런 약점없이 JSF의 모든 장점을 활용할 수 있다.

스프링 Faces는 표준 JSF 기능에 다음과 같은 강력한 보완재를 제공한다.

  1. managed bean facility
  2. scope management
  3. event handling
  4. navigation rules
  5. easy modularization and packaging of views
  6. cleaner URLs
  7. model-level validation
  8. client-side validation and UI enhancement
  9. Ajax partial page updates and full navigation
  10. progressive enhancement and graceful degradation

이 기능을 사용하면 faces-config.xml에 필요한 설정 분량을 현격하게 줄여줄 것이며 뷰와 컨트롤러 계층을 보다 깔끔하게 분리해주며 애플리케이션의 기능 책임 모듈화를 보다 잘 지원한다. 이 기능들의 사용법은 다음 절에서 살펴보겠다. 이 기능들 대부분은 스프링 웹 플로우의 플로우 정의 언어를 기반으로 한다. 여러분이 플로우 정의하기에 나와있는 기본을 이해하고 있다고 가정한다.

12.3. web.xml 설정하기

스프링 Faces를 사용하는 첫 번째 단계는 요청을 web.xml 파일에 있는 DispatcherServlet으로 라우팅하는 것이다. 이번 예제에서, 우리는 /spring/으로 시작하는 모든 URL을 서블릿으로 맵핑한다. 서블릿을 설정해야 한다. init-param을 사용하여 서블릿에 contextConfigLocation을 넘겨준다. 이 것은 애플리케이션의 스프링 설정 파일 위치다.

<servlet>
    <servlet-name>Spring MVC Dispatcher Servlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/web-application-config.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
   
<servlet-mapping>
    <servlet-name>Spring MVC Dispatcher Servlet</servlet-name>
    <url-pattern>/spring/*</url-pattern>
</servlet-mapping>
       
JSF를 적절하게 동작시키려면, FacesServlet을 web.xml에 설정해야 한다. 스프링 Faces를 사용할 때는 그것을 사용하여 요청을 라우팅할 필요가 없다.

<!– Just here so the JSF implementation can initialize, *not* used at runtime –>
<servlet>
    <servlet-name>Faces Servlet</servlet-name>
    <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>
   
<!– Just here so the JSF implementation can initialize –>
<servlet-mapping>
    <servlet-name>Faces Servlet</servlet-name>
    <url-pattern>*.faces</url-pattern>
</servlet-mapping>
       
스프링 Faces  컴포넌트를 사용할 때에도 스프링 자바스크립트 ResourceServlet을 설정하여 컴포넌트에서 CSS와 자바스크립트 리소스를 제대로 출력할 수 있게 할 필요가 있다. 이 서블릿은 반드시 /resources/*로 맵핑해야 컴포넌트에서 랜더링한 URL에 대해 제대로 동작한다.

<!– Serves static resource content from .jar files such as spring-faces.jar –>
<servlet>
    <servlet-name>Resource Servlet</servlet-name>
    <servlet-class>org.springframework.js.resource.ResourceServlet</servlet-class>
    <load-on-startup>0</load-on-startup>
</servlet>
       
<!– Map all /resources requests to the Resource Servlet for handling –>
<servlet-mapping>
    <servlet-name>Resource Servlet</servlet-name>
    <url-pattern>/resources/*</url-pattern>
</servlet-mapping>
       
스프링 Faces 컴포넌트는 JSP 대신에 Facelet을 사용해야 한다. 따라서 이들 컴포넌트를 사용하려면 일반적인 Facelet 설정을 반드시 추가해야 한다.

!– Use JSF view templates saved as *.xhtml, for use with Facelets –>
<context-param>
    <param-name>javax.faces.DEFAULT_SUFFIX</param-name>
    <param-value>.xhtml</param-value>
</context-param>
       
12.4. JSF 뷰 랜더링 하도록 웹 플로우 설정하기

The next step is to configure Web Flow to render JSF views. To do this, in your Spring Web Flow configuration include the faces namespace and link in the faces flow-builder-services :

다음 단계는 JSF 뷰를 랜더링 하도록 웹 플로우를 설정하는 것이다. 그렇게 하려면 스프링 웹 플로우 설정에 faces 네임스페이스를 추가하고

<?xml version=”1.0″ encoding=”UTF-8″?>
<beans xmlns=”http://www.springframework.org/schema/beans”
       xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
       xmlns:webflow=”http://www.springframework.org/schema/webflow-config”
       xmlns:faces=”http://www.springframework.org/schema/faces”
       xsi:schemaLocation=”
           http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/webflow-config
           http://www.springframework.org/schema/webflow-config/spring-webflow-config-2.0.xsd
           http://www.springframework.org/schema/faces
           http://www.springframework.org/schema/faces/spring-faces-2.0.xsd”>

    <!– Executes flows: the central entry point into the Spring Web Flow system –>
    <webflow:flow-executor id=”flowExecutor” />

    <!– The registry of executable flow definitions –>
    <webflow:flow-registry id=”flowRegistry” flow-builder-services=”facesFlowBuilderServices” base-path=”/WEB-INF”>
        <webflow:flow-location-pattern value=”**/*-flow.xml” />
    </webflow:flow-registry>

    <!– Configures the Spring Web Flow JSF integration –>
    <faces:flow-builder-services id=”facesFlowBuilderServices” />

</beans>
       
faces:flow-builder-services 태그는 JSF 환경에 적절한 다른 기본 값들도 설정한다. 특히 Unified EL을 기본 EL로 설정한다.

완전히 동작하는 예제는 배포판에서 swf-booking-faces 레퍼런스 애플리케이션을 참고하라.

12.5. faces-config.xml 설정하기

faces-config.xml에 유일하게 설정할 필요가 있는 것은 Facelet 사용에 대한 것이다. 만약 스프링 Faces 컴포넌트를 사용하지 않고 JSP를 사용하고 있다면, 어떠한 스프링 Faces 관련 설정도 faces-config.xml에 추가하지 않아도 된다.

<faces-config>
    <application>
        <!– Enables Facelets –>
        <view-handler>com.sun.facelets.FaceletViewHandler</view-handler> 
    </application>
</faces-config>
       
12.6. JSF가 관리하는 빈 기능 교체하기

스프링 Faces는 JSF가 관리하는 빈 기능을 플로우가 관리하는 변수와 스프링이 관리하는 빈으로 완전히 교체할 수 있게 해준다. 그렇게 하여 여러분이 관리하는 객체를 잘 정의되어 있는 초기화 후크와 도메인 객체 실행 후크로 생명 주기를 보다 잘 관리할 수 있다. 게다가, 여러분이 이미 비즈니스 계층에서 스프링을 사용해봤다는 가정한다면, 두 개의 다른 빈 관리 모델을 유지하는 것에 대한 개념적인 오버헤드를 줄일 수 있다.

순수 JSF 개발을 할 떄는 request 스코프로는 복잡한 이벤트-기반 뷰를 다루는 대화형 모델 객체를 저장하기에는 적당하지 않다는 것을 금방 알 수 있을 것이다. 오직 사용할 수 있는 옵션은 session 스코프에 모든 것들을 넣는 것이다. 애플리케이션의 다른 뷰 또는 기능적인 부분을 처리할 때 객체들을 청소해줘야 한다는 추가 작업이 생긴다. 정말로 필요한 것은 request와 session 스코프 중간 쯤 되는 어떤 스코프가 필요하다. 우누 좋게도 웹 플로우는 그러한 확장 기능을 제공한다.

12.6.1. 플로우 변수 사용하기

가장 간단하고 자연스럽게 모델을 선언하고 관리하는 것은 플로우 변수를 사용하는 것이다. 이 변수들을 플로우 시작시에 선언할 수 있다.

<var name=”searchCriteria” class=”com.mycompany.myapp.hotels.search.SearchCriteria”/>
           
그런 다음 이 변수를 플로우 중 어떤 JSF 뷰 템플릿에 EL을 통해서 참조한다.

<h:inputText id=”searchString” value=”#{searchCriteria.searchString}”/>
           
(좀 더 구체적일 필요가 있다면 그렇게 할 수도 있지만)템플릿에서 변수를 참조할 때 그 스코프로 접두어를 붙이지 않아도 된다는 것에 주목하라. 표준 JSF 빈으로 대응하는 변수에 대해 모든 사용 가능한 스코프에서 찾아볼 것이다. 따라서 EL 표현식을 수정하지 않고도 플로우 정의에서 그것을 참조하는 변수의 스코프를 변경할 수 있다.

또한 뷰 인스턴스 변수를 현재 뷰로 범위를 제한하고 다른 뷰로 이동하면 자동으로 비워버리게 정의할 수 있다. 이렇게 하는 것은 보통 페이지 내에서 다른 뷰로 전이하기 전에 여러 여러 요청에 걸쳐 이벤트를 처리하는 JSF 뷰에서 매우 유용하다.

뷰 인스턴스 변수를 정의할때 var 엘리먼트를 view-state 정의 내부에서 사용할 수 있다.

<view-state id=”enterSearchCriteria”>
    <var name=”searchCriteria” class=”com.mycompany.myapp.hotels.search.SearchCriteria”/>
</view-state>
           

12.6.2. 스코프를 가진 스프링 빈 사용하기

비록 자동 연결 플로우 인스터스 변수가 괜찮은 모듈화와 가독성을 제공하지만 가끔 여러분은 AOP 같은 스프링 컨테이너의 기능을 활용하고 싶을 수 있다. 그런 경우 여러분은 스프링 애플리케이션컨텍스트 내부에 있는 빈에 특정 웹 플로우 스코프를 줄 수 있다.

<bean id=”searchCriteria” class=”com.mycompany.myapp.hotels.search.SearchCriteria” scope=”flow”/>
           
이런 접근 방법의 가장 큰 차이점은 EL 표현식에 의해 접근될 때 비을 초기화 한다는 것이다. EL을 통한 이런 종류의 생성 지연은 JSF가 관리하는 빈이 할당되는 것과 매우 비슷하다.

12.6.3. 모델 조작하기

뷰 랜터링 하기 전에 (데이터베이스에서 영속 엔티티를 로딩하는 것 같은)모델 초기화가 필요한 것은 매우 흔한 일이지만 JSF 자체로는 이러한 초기화에 사용할 편의성 후크(hook)를 제공하지 않는다. 플로우 정의 언어는 액션(Action)을 통해서 이와 관련된 자연스러운 기능을 제공한다. 스프링 Faces는 액션의 결과를 JSF-관련 데이터 구조로 변경해주는 추가적인 편의성을 제공한다. 예제를 보자.

<on-render>
    <evaluate expression=”bookingService.findBookings(currentUser.name)”
              result=”viewScope.bookings” result-type=”dataModel” />
</on-render>
         
이렇게 하면 bookingService.findBookings 매서드 결과를 취하고 그것을 표준 JSF DataTable 컴포넌트에서 해당 목록을 사용할 수 있도록 커스텀 JSF DataModel로 감싼다.

<h:dataTable id=”bookings” styleClass=”summary” value=”#{bookings}” var=”booking”
             rendered=”#{bookings.rowCount > 0}”>
    <h:column>
        <f:facet name=”header”>Name</f:facet>
        #{booking.hotel.name}
    </h:column>                  
    <h:column>
    <f:facet name=”header”>Confirmation number</f:facet>
        #{booking.id}
        </h:column>
    <h:column>
        <f:facet name=”header”>Action</f:facet>
        <h:commandLink id=”cancel” value=”Cancel” action=”cancelBooking” />
    </h:column>
</h:dataTable>
           
커스텀 DataModel은 request 스코프 외의 저장을 위한 직렬화와 EL에서 현재 선택한 줄에 대한 접근 등 몇몇 추가적인 편의성을 제공한다. 예를 들어, DataTable 내의 컴포넌트에 의해 액션 이벤트가 발생한 뷰에서 포스트백 할 때, 여러분은 선택한 줄의 모델 인스턴스를 가질 수 있다.

<transition on=”cancelBooking”>
    <evaluate expression=”bookingService.cancelBooking(bookings.selectedRow)” />           
</transition>
           
12.7. 스프링 웹 플로우로 JSF 이벤트 처리하기

스프링 웹 플로우는 낮은 결합도를 유지하면서 JSF 액션 이벤트를 처리할 수 있게 해준다. 자바 코드에서 JSF API에 의존하지 않아도 된다. 이벤트를 커스텀 자바 액션 코드를 전혀 사용하지 않고 플로우 정의 언어를 사용하여 완전하게 처리할 수 있다. 이렇게 하면 (JSF 뷰 템플릿과 SWF 플로우 정의) 이벤트를 연결할 때 만들어지는 구성물들을 전체 애플리케이션을 빌드하고 다시 배포할 필요 없이 즉시 리프래시 되기 때문에 보다 기민한 개발 프로세스가 가능해진다.

12.7.1. JSF In-page 액션 이벤트 처리하기

간단하지만 JSF에서 가장 흔한 경우가 모델을 조작하는 이벤트를 발생시키고 동일한 뷰로 모델의 변경된 상태를 보여주는 것이다. 플로우 정의 언어는 transition 엘리먼트에서 이것을 지원한다.

이 것에 대한 좋은 예제는 페이징 처리를 하는 목록 표다. 거대한 결과 목록 중의 일부만 읽어오고 보여주고 싶다고 가정해보자. 그리고 사용자는 그 결과를 페이징할 수 있다. 목록을 읽어오고 보여주는 초기 view-state 정의는 다음과 같다.
 
<view-state id=”reviewHotels”>
    <on-render>
        <evaluate expression=”bookingService.findHotels(searchCriteria)”
                  result=”viewScope.hotels” result-type=”dataModel” />
    </on-render>
</view-state>
           

JSF DataTable로 현재 호텔 목록을 보여주도록 할 수 있다. 그런 다음 표 하단에 “More Results” 링크를 제공한다.
 
<h:commandLink id=”nextPageLink” value=”More Results” action=”next”/>
           
이 커맨드링크는 action 속성에서 “next” 이벤트를 보낸다. 그럼 여러분은 이벤트를 view-state 정의에 추가하려 처리할 수 있다.
 
<view-state id=”reviewHotels”>
    <on-render>
        <evaluate expression=”bookingService.findHotels(searchCriteria)”
            result=”viewScope.hotels” result-type=”dataModel” />
    </on-render>
    <transition on=”next”>
        <evaluate expression=”searchCriteria.nextPage()” />
    </transition>
</view-state>
           
여기서 searchCriteria 인스턴스에서 page 카운트를 증가하여 “next” 이벤트를 처리한다. 그럼 다음 on-render 액션을 변경된 criteria로 호출한다. 그러면 다음 페이지 결과를 DataModel로 로딩해준다. transition  엘리먼트에 to 속성이 없기 때문에 동일한 뷰를 다시 보여준다. 그리고 모델에 변경된 사항을 뷰에 반영해준다.

12.7.2. JSF 액션 이벤트 처리하기

in-page 이벤트 다음 단계는 조작한 모델을 가지고 다른 뷰로 이동하는 이벤트다. 순수 JSF로 이것을 하려면 faces-config.xml에 네비게이션 로직을 추가하고 자바 코드를 JSF가 관리하는 빈에 추가해야 한다.(두 작업 모두 다시 배포해야 한다.) 플로우 정의 언어를 사용하면, in-page 이벤트를 다루던 방식과 매우 비슷하게 한 곳에서 그런 것을 다룰 수 있다.

계속해서 페이징 처리하는 목록을 살펴보자. 보여지는 DataTable의 각각의 row에 row 인스턴스에 대한 자세한 내용 페이지 링크를 가지고 있도록 하고자 한다. 여러분은 테이블에 다음의 commandLink 컴포넌트를 가지고 있는 컬럼을 추가할 수 있다.

<h:commandLink id=”viewHotelLink” value=”View Hotel” action=”select”/>
           
이것은 “select” 이벤트를 발생시킨다. 그다음 기존의 view-state에 또 다른 transition 엘리먼트를 추가하여 이것을 처리할 수 있다.
 
<view-state id=”reviewHotels”>
    <on-render>
        <evaluate expression=”bookingService.findHotels(searchCriteria)”
            result=”viewScope.hotels” result-type=”dataModel” />
    </on-render>
    <transition on=”next”>
        <evaluate expression=”searchCriteria.nextPage()” />
    </transition>
    <transition on=”select” to=”reviewHotel”>
            <set name=”flowScope.hotel” value=”hotels.selectedRow” />
    </transition>
</view-state>
           
여기서 “select” 이벤트는 DataTable에서 현재 선택한 hotel 인스턴스를 플로우 스코프에 넣어서 처리하고 있다. 그렇게 하면 “reviewHotel” view-state에서 참조할 것이다.

12.7.3. 모델 검증 수행하기

JSF는 변경 사항을 모델에 반영하기 전에 필드-수준 입력 검증 관련 유용한 기능을 제공한다. 하지만 변경 사항을 적용한 뒤에 모델-수준의 좀 더 복잡한 검증을 수행할 필요가 있다면 여러분은 관리하는 빈의 JSF 액션 매서드에 커스텀 코드를 추가해야 한다. 이런 종류의 검증은 모메인 모델 자체 책임이지만 도메인 모델 계층에 원하지 않던 JSF API 의존성을 추사하지 않고서는 에러 메시지를 뷰에 전달하기가 어렵다.

스프링 Faces를 사용하면 일반적이고 낮은-수준의 MessageContext를 여러분의 비즈니스 코드에서 유용하게 사용할 수 있고 그곳에 추가한 모든 메시지는 랜더링 시에 FacesContext에서 사용할 수 있다.

예를 들어, 사용자가 호텔 예약을 완료하기 위해 필요한 상세 정보를 입력하는 뷰가 있다고 가정하자. 여러분은 거기서 입력받은 체크인 체크아웃 날짜가 비즈니스 규칙에 맞는지 확인해야 한다. transition 엘리먼트에서 그러한 모델-수준 검증을 호출할 수 있다.
 
<view-state id=”enterBookingDetails”>
    <transition on=”proceed” to=”reviewBooking”>
        <evaluate expression=”booking.validateEnterBookingDetails(messageContext)” />
    </transition>
</view-state>
          
여기서 “proceed” 이벤트는 booking 인스턴스의 모델-수준 검증 매서드를 호출하여 처리한다. 매서드를 호출할 때 MessageContext 인스턴스를 넘겨줘서 메시지를 기록할 수 있게 한다. 그런다음 h:messages 컴포넌트를 사용하여 JSF 메시지를 보여줄 수 있다.

12.7.4. Ajax 이벤트 처리하기

스프링 Faces는 표준 JSF 컴포넌트에 Ajax-기반 일부 뷰 수정 기능을 추가한 몇몇 특별한 UICommand 컴포넌트를 제공한다. 이들 컴포넌트는 사용자가 사용하는 브라우져의 기능이 떨어진다면 전체 페이지를 리프래시 하여 모두 잘 동작할 것이다.

[노트] 노드
스프링 Faces의 코어 JSF 지원이 JSF 1.1 호환가능 하지만, 스프링 Faces Ajax 컴포넌트는 JSF 1.2를 필요로 한다.

Revisiting the earlier example with the paged table, you can change the “More Results” link to use an Ajax request by replacing the standard commandButton with the Spring Faces version (note that the Spring Faces command components use Ajax by default, but they can alternately be forced to use a normal form submit by setting ajaxEnabled=”false” on the component):

           
<sf:commandLink id=”nextPageLink” value=”More Results” action=”next” />
           

This event is handled just as in the non-Ajax case with the transition element, but now you will add a special render action that specifies which portions of the component tree need to be re-rendered:

<view-state id=”reviewHotels”>
    <on-render>
        <evaluate expression=”bookingService.findHotels(searchCriteria)”
                  result=”viewScope.hotels” result-type=”dataModel” />
    </on-render>
    <transition on=”next”>
        <evaluate expression=”searchCriteria.nextPage()” />
        <render fragments=”hotels:searchResultsFragment” />
    </transition>
</view-state>
           

The fragments=”hotels:searchResultsFragment” is an instruction that will be interpreted at render time, such that only the component with the JSF clientId “hotels:searchResultsFragment” will be rendered and returned to the client. This fragment will then be automatically replaced in the page. The fragments attribute can be a comma-delimited list of ids, with each id representing the root node of a subtree (meaning the root node and all of its children) to be rendered. If the “next” event is fired in a non-Ajax request (i.e., if JavaScript is disabled on the client), the render action will be ignored and the full page will be rendered as normal.

In addition to the Spring Faces commandLink component, there is a corresponding commandButton component with the same functionality. There is also a special ajaxEvent component that will raise a JSF action even in response to any client-side DOM event. See the Spring Faces tag library docs for full details.

An additional built-in feature when using the Spring Faces Ajax components is the ability to have the response rendered inside a rich modal popup widget by setting popup=”true” on a view-state .

<view-state id=”changeSearchCriteria” view=”enterSearchCriteria.xhtml” popup=”true”>
    <on-entry>
        <render fragments=”hotelSearchFragment” />
    </on-entry>
    <transition on=”search” to=”reviewHotels”>
        <evaluate expression=”searchCriteria.resetPage()”/>
    </transition>
</view-state>
           

If the “changeSearchCriteria” view-state is reached as the result of an Ajax-request, the result will be rendered into a rich popup. If JavaScript is unavailable, the request will be processed with a full browser refresh, and the “changeSearchCriteria” view will be rendered as normal.

SWF 11장 스프링 자바스크립트 퀵 레퍼런스

11.1 도입

스프링 자바스크립트(spring-js)는 Dojo 같이 자주 사용하는 자바스크립트 툴킷에 대한 경량
추상화다. 목표는 공통의 클라인트-쪽 프로그래밍 모델을 제공하여 웹 페이지를 리치 위젯과 애이작스 리모팅으로 급격히 개선하는
것이다.

11.2. 자바스크립트 리소스 제공하기

스프링 JS는 웹 애플리케이션 루트 디렉토리와 jar
파일에서 자바스크립트와 CSS 파일 같은 웹 리소스를 서빙하는 ResourceServlet를 제공한다. 이 서블릿은
Spring.js 파일을 여러분 페이지에 공급하는 편리한 방법을 제공한다. 이 서블릿을 배포하려면 web.xml에 다음과 같이
설정하라.

<!– Serves static resource content from .jar files such as spring-js.jar –>
<servlet>
    <servlet-name>Resource Servlet</servlet-name>
    <servlet-class>org.springframework.js.resource.ResourceServlet</servlet-class>
</servlet>
       
<!– Map all /resources requests to the Resource Servlet for handling –>
<servlet-mapping>
    <servlet-name>Resource Servlet</servlet-name>
    <url-pattern>/resources/*</url-pattern>
</servlet-mapping>
       
11.3. 스프링 자바스크립트를 페이지에 포함시키기

스프링 JS는 자신의 API 구현체를 유명한 자바스크립트 툴킷으로 빌드 할 수 있게 설계됐다. Spring.js 기본 구현체는 Dojo 툴킷을 기반으로 한다.


프링 자바스크립트를 페이지에서 사용하려면 보통 기반으로 하는 툴킷, Spring.js 기반 인터페이스 파일, 스프링-(라이브러리
구현체).js 파일을 포함시킬 필요가 있다. 예를 들어 다음은 ResourceServlet를 사용하여 Dojo 구현체를 가져온다.

<script type=”text/javascript” src=”<c:url value=”/resources/dojo/dojo.js” />”> </script>
<script type=”text/javascript” src=”<c:url value=”/resources/spring/Spring.js” />”> </script>
<script type=”text/javascript” src=”<c:url value=”/resources/spring/Spring-Dojo.js” />”> </script>
       

반 라이브러리의 위젯 시스템을 사용하면 보통 여러분은 반드시 원하는 룩앤필(look and feel)을 얻기 위해 CSS
리소스를 포함시킬것이다. booking-mvc 레퍼런스 애플리케이션은 Dojo의 tundra.css를 포함시킨다.

<link type=”text/css” rel=”stylesheet” href=”<c:url value=”/resources/dijit/themes/tundra/tundra.css” />” />
       
11.4. 스프링 자바스크립트 데코레이션


프링 자바스크립트의 주요 개념은 기존 DOM 노드에 데코레이션을 적용하는 개념이다. 이 기술은 기능이 좋치 않은 브라우저에서도
여전히 동작할 페이지 같은 웹 페이지를 혁신적으로 개선할 때 사용한다. addDecoration 매서드는 데코레이션을 적용할 때
사용한다.

다음 예제는 스프링 MVC의 <form:input> 태그에 풍부한 제안 기능을 가능하게 하는 것을 보여준다.

<form:input id=”searchString” path=”searchString”/>
<script type=”text/javascript”>
    Spring.addDecoration(new Spring.ElementDecoration({
        elementId: “searchString”,
        widgetType: “dijit.form.ValidationTextBox”,
        widgetAttrs: { promptMessage : “Search hotels by name, address, city, or zip.” }}));
</script>
       
ElementDecoration
는 리치 위젯 기능을 기존의 DOM 노드에 추가할 때 사용한다. 이 데코레이션 타입은 기반으로 하는 툴킷을 완전히 감추는 것을
목표로 하지 않는다. 따라서 툴킷의 네이티브 위젯 타입과 속성을 직접 사용한다. 이 방법은 공통의 데코레이션 모델을 사용하여
기반하는 툴킷의 모든 위젯을 일관적인 방법으로 통합하기 위한 것이다. booking-mvc 레퍼런스 애플리케이션에서 제안에서
부터 클라이언트쪽 검증까지 데코레이션을 적용하는 많은 예제를 참조하라.

ElementDecoration를 사용하여 리치 검증 기능을 갖춘 위젯을 적용할 때 일반적인 요구 사항은 폼이 검증을 통과하기 전까지는 서브밋하지 않는 것이다. ValidateAllDecoration으로 그것을 할 수 있다.

<input type=”submit” id=”proceed” name=”_eventId_proceed” value=”Proceed” />
<script type=”text/javascript”>
    Spring.addDecoration(new Spring.ValidateAllDecoration({ elementId:’proceed’, event:’onclick’ }));
</script>
       

이것은 “Proceed” 버튼을 클라이언트 쪽 검증을 하고 성공적으로 패스 할 때 까지 폼 서브밋을 허용하지 않는 특별한 onclick 이벤트 핸들러로 데코레이트한다.

AjaxEventDecoration는 원격 애이작스 요청을 서버로 보내는 클라이언트쪽 이벤트 리스너를 적용한다. 또한 응답할 때 자동으로 콜백 함수를 링크에 등록한다.

<a id=”prevLink” href=”search?searchString=${criteria.searchString}&page=${criteria.page – 1}”>Previous</a>
<script type=”text/javascript”>
    Spring.addDecoration(new Spring.AjaxEventDecoration({
        elementId: “prevLink”,
        event: “onclick”,
        params: { fragments: “body” }
    }));
</script>
       

것은 “Previous Results” 링크의 onclick 이벤트를 응답시에 특정 조각을 다시 랜더링하도록 특별한 매개변수와
함께 애이작스 호출로 꾸며준다. 이 링크는 클라이언트에서 자바스크립트를 사용할 수 없는 경우에도 여전히 동작한다는 것에
주목하라. (애이작스 요청 다루기에서 서버에 요청을 어떻게 다루는지 자세히 살펴보라.)

또한 하나의 엘리먼트에 여러 개의 데코레이션을 적용하는 것도 가능하다. 다음 예제는 애이작스와 validate-all 서브밋 금지를 같이 적용한 버튼에 대한 예제다.

<input type=”submit” id=”proceed” name=”_eventId_proceed” value=”Proceed” /> 
<script type=”text/javascript”>
    Spring.addDecoration(new Spring.ValidateAllDecoration({elementId:’proceed’, event:’onclick’}));
 
  Spring.addDecoration(new
Spring.AjaxEventDecoration({elementId:’proceed’,
event:’onclick’,formId:’booking’, params:{fragments:’messages’}}));
</script>
       
Dojo의 쿼리 API를 사용하여 한 줄로 여러 엘리먼트에 하나의 데코레이션을 적용하는 것도 가능하다. 다음 예제는 체크박스 집합을 Dojo Checkbox 위젯으로 꾸민다.

<div id=”amenities”>
<form:checkbox path=”amenities” value=”OCEAN_VIEW” label=”Ocean View” /></li>
<form:checkbox path=”amenities” value=”LATE_CHECKOUT” label=”Late Checkout” /></li>
<form:checkbox path=”amenities” value=”MINIBAR” label=”Minibar” /></li>
<script type=”text/javascript”>
    dojo.query(“#amenities input[type=’checkbox’]”).forEach(function(element) {
        Spring.addDecoration(new Spring.ElementDecoration({
            elementId: element.id,
            widgetType : “dijit.form.CheckBox”,
            widgetAttrs : { checked : element.checked }
        }));
    });
</script>
</div>

11.5. 애이작스 요청 다루기

스프링 자바스크립트의 클라이언트 쪽 애이작스 응답 다루기는 서버에서 “조각” 받기 개념을 기반으로 했다. 이들 조각은 단순한
표준 HTML로 기존 페이지의 일부를 교체할 의도를 가지고 있다. 서버에서 필요한 핵심 조각은 전체 응답 중에서 어떤 조각을
일부만 랜더링 해야 하는지 판단할 때 사용한다.

전체 응답 중에 일부 조각만 랜더링 하려면 전체 응답은 반드시 응답 구성할 때 컴포지션을 사용할 수 있고 그 컴포지션의 구성
요소들은 개별적으로 참조하거나 랜더링 할 수 있는 템플릿 기술을 사용해서 만들어야 한다. 스프링 자바스크립트는
타일즈(Tiles)를 사용하는 간단한 스프링 MVC 확장을 통해 이것을 달성한다. 컴포지션을 지원하는 어떤 템플릿 시스템이든지
이론적으로는 같은 기술로 사용할 수 있다.

스프링 자바스크립트의 애이작스 리모팅 기능은 애이작스 요청을 다루는 코드가 일반 브라우저 요청과 다르지 않아야 한다는 개념을
기반으로 한다. 따라서 애이작스 요청에 대한 별도의 지식은 코드에서 직접적으로 필요하지 않고 동일한 핸들러를 두 가지 스타일의
요청에 모두 사용할 수 있다.

11.5.1. 스프링 MVC 컨트롤러로 애이작스 요청 다루기

애이작스 요청을 스프링 MVC 컨트롤러로 다루려면 스프링 MVC 확장이 제공하는 설정을 여러분의 스프링 애플리케이션 컨텍스트에 일부 응답을 랜더링 하도록 추가하면 된다.

<bean id=”tilesViewResolver” class=”org.springframework.js.ajax.AjaxUrlBasedViewResolver”>
    <property name=”viewClass” value=”org.springframework.webflow.mvc.view.FlowAjaxTilesView”/>
</bean>
           
이것은 AjaxUrlBasedViewResolver를 설정하여 애이작스 요청을 분석하고 FlowAjaxTilesView 객체를
만들어서 적절한 부분을 랜더링하는 것을 처리하게 한다. FlowAjaxTilesView는 웹 플로우와 순수 스프링 MVC 요청을
모두 랜더링하는 작업을 할 수 있다. 조각은 타일즈 뷰 정의의 개별 속성에 대응한다. 예를 들어, 다음의 타일즈 뷰 정의를 보자.

<definition name=”hotels/index” extends=”standardLayout”>
    <put-attribute name=”body” value=”index.body” />
</definition>

<definition name=”index.body” template=”/WEB-INF/hotels/index.jsp”>
    <put-attribute name=”hotelSearchForm” value=”/WEB-INF/hotels/hotelSearchForm.jsp” />
    <put-attribute name=”bookingsTable” value=”/WEB-INF/hotels/bookingsTable.jsp” />
</definition>
           
애이작스 요청은 “body”, “hotelSearchForm”, “bookingsTable”를 기술하여 요청에서 일부를 랜더링할 수 있다.

11.5.2. 스프링 MVC + 스프링 웹 플로우에서 애이작스 요청 다루기

스프링 웹 플로우는 조각의 부가적인 랜더리을 플로우 정의 언어에서 render 엘리먼트로 직접 다룬다. 이 방법의 장점은 조각의
선택이 클리이언트 쪽 코드와 완전히 분리 된다는 것이다. 스프링 MVC 컨트롤러 접근 방법에서 사용하듯이 요청에 넘겨주는 별도의
매개변수가 필요 없다. 예를 들어, 만약 앞선 예제 타일즈 뷰를 리치 자바스크립트 팝업으로 “hotelSearchForm”
조각을 랜더링 하고 싶다면 다음과 같이 할 수 있다.

<view-state id=”changeSearchCriteria” view=”enterSearchCriteria.xhtml” popup=”true”>
    <on-entry>
        <render fragments=”hotelSearchForm” />
    </on-entry>
    <transition on=”search” to=”reviewHotels”>
        <evaluate expression=”searchCriteria.resetPage()”/>
    </transition>
</view-state>