메인 컨텐츠로 가기

developerWorks 이용 약관에 동의하시는 경우 제출을 클릭하십시오. 이용 약관 보기.

developerWorks에 처음 로그인하면 developerWorks프로파일이 생성됩니다.귀하의 프로파일에서 동의하신 내용이 공개되지만 이 사항은 언제든지 변경 가능합니다. 귀하의 성명(숨김으로 체크되어 있어도 표시됩니다)과 디스플레이 이름은 게시한 컨텐츠나 사이트 엑세스시 표시됩니다.

모든 정보가 안전하게 전송되었습니다.

  • 닫기 [x]

처음 developerWorks에 로그인할 때 프로파일이 작성되므로, 이를 위해 디스플레이 이름을 선택해야 합니다. 선택하신 디스플레이 이름은 developerWorks에 게시한 컨텐츠에 표시됩니다.

3글자 이상 31글자 이하의 길이로 사용 가능합니다. dW커뮤니티 내에서는 보안상 이메일주소를 제외한 다른 이름을 지정하셔야 합니다.

developerWorks 이용 약관에 동의하시는 경우 제출을 클릭하십시오. 이용 약관 보기.

모든 정보가 안전하게 전송되었습니다.

  • 닫기 [x]

JSF 2 fu: HTML5 복합 컴포넌트, Part 2

끌어서 놓기 구현

David Geary, Chủ tịch, Clarity Training, Inc.
David Geary
David Geary, là tác giả, diễn giả và nhà tư vấn, là chủ tịch của Clarity Training, Inc., nơi ông dạy cho các nhà phát triển cách triển khai thực hiện các ứng dụng Web bằng cách sử dụng JSF và Bộ công cụ Web của Google (Google Web Toolkit-GWT). Ông đã ở trong nhóm chuyên gia JSTL 1.0 và JSF 1.0/2.0, đồng tác giả của kỳ thi lấy chứng chỉ nhà phát triển Web của Sun và đã đóng góp vào các dự án mã nguồn mở, bao gồm Apache Struts và Apache Shale. Cuốn Graphic Java Swing của David đã là một trong những cuốn sách Java bán chạy nhất mọi thời đại và Core JSF (viết cùng với Cay Horstman), là cuốn sách JSF bán chạy nhất. David thuyết trình thường xuyên tại các hội nghị và các nhóm người dùng. Ông đã thường xuyên tham gia các chuyến đi NFJS từ năm 2003, đã dạy các khóa học tại Đại học Java và đã hai lần được bình chọn là một ngôi sao rock của JavaOne.

요약:  이번 JSF 2 fu 기사에서 이 시리즈의 작성자인 David Geary는 JSF(Java™Server Faces) 2 기술을 HTML5와 결합했을 때의 강점을 계속해서 설명합니다. 이번에는 HTML5 끌어서 놓기를 캡슐화하는 JSF 복합 컴포넌트를 구현하는 방법을 살펴봅니다.

이 연재 자세히 보기

원문 게재일:  2010 년 11 월 23 일 번역 게재일:  2011 년 4 월 12 일
난이도: 중급 원문:  보기 PDF:  A4 and Letter (603KB | 24 pages)Get Adobe® Reader®
페이지뷰:  2250 회
의견:  


이 시리즈의 정보

같은 이름으로 소개된 David Geary의 세 기사의 후속 기사인 이 JSF 2 fu 시리즈를 통해 독자는 JSF 2 프레임워크 기술을 개발하고 다듬을 수 있다. 현재 작성 중인 시리즈에서는 프레임워크와 주위 환경에 대해 더 자세히 살펴본다. 또한 이 시리즈에서는 CDI(Contexts and Dependency Injection)와 같은 일부 Java EE 기술을 JSF와 통합되는 방법을 살펴보며 기사의 범위를 벗어난 내용도 일부 설명한다.

이번 기사의 전제조건인 JSF 2 fu 이전 시리즈에서는 HTML5의 기능을 소개했으며 HTML5 캔버스를 사용하기 쉽게 해주는 JSF 복합 컴포넌트를 빌드하는 방법을 살펴보았다. 이번 기사에서는 HTML5의 이벤트 기반 끌어서 놓기 메커니즘(참고자료 참조)을 활용하는 JSF 복합 컴포넌트(<h5:drag><h5:drop>)를 구현하는 과정을 살펴본다.

끌어서 놓기 복합 컴포넌트에는 다섯 가지 중요한 기능이 있다.

  • 사용 용이성
  • 조건부 끌기
  • Ajax
  • 부분 렌더링
  • 페이로드

<h5:drag><h5:drop>은 가장 기본적인 레벨에서 HTML5 끌어서 놓기의 일부 변화를 캡슐화하여 사용을 더욱 용이하게 한다. 예를 들면, 브라우저에서는 기본적으로 끌어서 놓기를 할 수 없다. 따라서 drag-enter와 drag-over 이벤트 핸들러에서 이 이벤트를 취소하여 브라우저가 기본적으로 이 이벤트에 반응하지 않도록 해야 한다. 이러한 비직관적인 작업은 <h5:drag> 컴포넌트에서 처리되며 따라서 페이지 작성자는 더욱 의미 있는 작업에 집중할 수 있게 된다.

<h5:drag><h5:drop> 컴포넌트는 조건부 끌기를 지원한다. 페이지 작성자는 전달되는 데이터와 문제가 되는 놓기 대상을 기반으로 놓기를 승인하거나 거부할 수 있다.

샘플 코드 실행하기

이 시리즈에 사용된 코드는 GlassFish 또는 Resin과 같은 엔터프라이즈 컨테이너에서 실행 중인 JSF 2를 기반으로 한다. GlassFish를 사용하여 이 시리즈에 있는 코드를 설치하고 실행하는 데 필요한 단계별 튜토리얼은 이 시리즈의 첫 번째 기사인 ""JSF 2 fu: Ajax components"를 참조한다. 이 기사에 사용된 샘플 코드를 얻으려면 다운로드를 참조한다.

JSF 애플리케이션에서는 사용자가 조작하는 대부분의 데이터가 일반적으로 서버에 관리 빈으로 저장된다. 이 때문에 <h5:drop> 컴포넌트는 놓기가 승인되는 경우에 Ajax를 호출한다. 페이지 작성자는 Ajax 호출이 리턴될 때 JSF에서 렌더링할 컴포넌트를 지정할 수 있다.

또한, <h5:drag><h5:drop> 컴포넌트는 일반적으로 페이로드라고 하는 일부 데이터를 끌어서 놓기 동작에 첨부하는 기능을 지원한다. 페이지 작성자는 페이로드 역할을 하는 빈 특성을 지정한다. 놓기가 일어날 때, <h5:drop> 컴포넌트에 의해 시작된 Ajax 호출 과정에서 JSF는 페이로드 빈 특성에 맞는 세터 메소드를 호출한다. 그러므로 JSF는 끌어서 놓기 페이로드를 <h:inputText> 요소의 값처럼 처리한다.

끌기 소스와 놓기 대상 사용하기

<h5:drag><h5:drop>은 각각 HTML5 끌기 소스와 놓기 대상을 나타낸다. 이러한 컴포넌트를 사용하는 JSF 애플리케이션에서는 다음과 같은 끌기 소스를 사용한다.

<script>
   function dragStart(event) {
      event.dataTransfer.setData('text', "transfer this string");
   }
</script>

<h5:drag ondragstart="dragStart(event)">

   ...

</h5:drag>

페이지 작성자는 일부 컴포넌트나 HTML 요소를 <h5:drag> 컴포넌트에 삽입할 수 있고 이전 마크업에서와 같이 ondragstart 함수에서 전달 데이터를 설정할 수 있다.

놓기 대상은 다음과 같이 사용한다.

<h5:drop id="dropzone" 
    payload="#{dragDrop.payload}" 
     render="@this"

   ...

</h5:drop>

끌기 소스를 사용할 때와 마찬가지로 페이지 작성자는 일부 컴포넌트나 HTML 요소를 <h5:drop> 컴포넌트에 삽입할 수 있다. 또한, 페이지 작성자는 끌어서 놓기 페이로드에 맞는 빈 특성을 지정하고, Ajax 호출을 할 때 JSF에서 렌러링해야 하는 컴포넌트를 지정한다. 이 Ajax 호출은 놓기가 수행된 후에 실행되고 서버에서 리턴된다.

또한, 페이지 작성자는 다음과 같이 약간의 Javascript를 사용하여 끌어서 놓기 동작에 선택적으로 개입할 수 있다.

<h5:drop id="dropzone" 
    payload="#{dragDrop.payload}" 
     render="@this"
 ondragover="dragover(event)"
ondragenter="dragenter(event)" 
ondragleave="dragleave(event)"
     ondrop="drop(event)">

   ...

</h5:drop>

이전 마크업에서는 dragover(), dragenter(), dragleave()drop() 함수(표시되지 않음)를 페이지 작성자가 구현했다.

이제까지 이 기사에서 다룰 내용을 살펴보았으므로 이제는 이 기사에 맞는 끌어서 놓기 유스 케이스, 즉 Feeds 애플리케이션을 논의하도록 하자.


Feeds 애플리케이션

그림 1에 있는 Feeds 애플리케이션은 RSS 피드 리더이다. 왼쪽 메뉴에는 RSS 피드 목록이 표시되며 사용자는 이 목록에 피드를 추가할 수 있다. 페이지 중간에는 현재 피드의 기사 링크가 표시된다. 사용자가 기사 링크를 클릭하면, 애플리케이션은 연관된 기사를 브라우저에 로드하며 사용자는 그 후에 Back 단추를 눌러서 애플리케이션으로 다시 돌아갈 수 있다.


그림 1. Feeds 애플리케이션
브라우저에서 열린 Feeds 애플리케이션의 스크린샷

특정 RSS 피드의 기사 목록은 정기적으로 업데이트되므로 바쁜 사용자는 그림 2에서와 같이 기사 링크를 애플리케이션 중앙에서 오른쪽 메뉴로 끌어서 링크를 저장했다가 나중에 읽을 수 있다.


그림 2. Feeds 애플리케이션에서의 끌어서 놓기
Feeds 애플리케이션에서의 끌어서 놓기

이 애플리케이션에는 RSS 피드를 읽어서 RSS 항목으로 구성한 다음 목록을 제공하는 관리 빈(목록 1)이 있다.


목록 1. RSS 피드 검색 및 구문 분석

package com.clarity;

import java.io.Serializable;

import java.net.URL;
import java.util.LinkedList;

import org.gnu.stealthp.rsslib.RSSChannel;
import org.gnu.stealthp.rsslib.RSSHandler;
import org.gnu.stealthp.rsslib.RSSItem;
import org.gnu.stealthp.rsslib.RSSParser;
import javax.enterprise.context.SessionScoped;
import javax.inject.Named;

@Named("rssFeed")
@SessionScoped
public class RSSFeed implements Serializable {
   private static final long serialVersionUID = 2L;
   
   private String feed, displayName;
   private RSSChannel channel;
   private LinkedList<RSSItem> savedItems = new LinkedList<RSSItem>();

   public void fetch(String f, String dn) {
      assert f != null;
      assert dn != null;

      feed = f;
      displayName = dn;

      RSSHandler handler = new RSSHandler();
      channel = handler.getRSSChannel();

      try {
         RSSParser.parseXmlFile(new URL(feed), handler, true);
      } catch (Exception e) {
         channel = null;
         e.printStackTrace();
      }
   }

   public LinkedList<RSSItem> getItems() {
      return channel == null ? null : channel.getItems();
   }
   public LinkedList<RSSItem> getSavedItems() { 
      return savedItems;   
   }

   public RSSChannel getChannel() { return channel; }
   public String getFeed() { return feed; }
   public String getDisplayName() { return displayName; }
}

RSSFeed 클래스에서는 RSS 피드를 쉽게 검색하고 구문 분석할 수 있게 해주는 RSSLib4J를 사용한다(참고자료 참조). 목록 1에 있는 @Named@SessionScoped 속성 덕택에 애플리케이션에는 RSSFeed 인스턴스인 세션 기반 관리 빈(rssFeed)이 존재한다.

이 애플리케이션 왼쪽 메뉴에 있는 모든 링크에는 rssFeed.fetch() 함수를 호출하는 조치가 있다. 예를 들면, 왼쪽 메뉴에 있는 Apple 링크는 다음과 같이 구현된다.

<h:commandLink value="Apple"
  action="#{rssFeed.fetch('http://rss.news.yahoo.com/rss/applecomputer', 
                          'Apple Computer')}"/>

사용자가 이 링크를 클릭하면 JSF는 rssFeed 관리 빈의 fetch() 메소드를 호출하고 이어서 애플리케이션 중앙에 표시된 링크 목록을 다시 로드한다.

<ui:repeat value="#{rssFeed.items}" var="item">
  <h5:drag ondragstart="dragStart(event)">
   <a href="#{item.link}">#{item.title}</a>
  </h5:drag>
<ui:repeat>

또한, 이 애플리케이션의 오른쪽 메뉴에는 저장된 링크가 표시된다.

<ui:repeat value="#{rssFeed.savedItems}" var="item">
   <a href="#{item.link}">
      #{ fn:substring(#{item.title}, 0, 25) } ...
   </a>
<ui:repeat>

이제까지 Feeds 애플리케이션이 RSS 피드 항목을 어떻게 검색하고 표시하는지 살펴보았으므로 이제 이 기사의 주요 이벤트로 관심을 돌려보자.


<h5:drag>와 <h5:drop> 컴포넌트

<h5:drag><h5:drop> 컴포넌트는 그림 3에서와 같이 세 개의 파일로 구현되며, drag.xhtml과 drop.xhtml은 각각 컴포넌트용이고 drop.js는 <h5:drop> 컴포넌트의 Javascript를 위한 것이다.


그림 3. <h5:drop><h5:drag> 컴포넌트용 파일
끌어서 놓기 컴포넌트의 폴더 계층 구조의 스크린샷

이 컴포넌트를 이용하면 끌어서 놓기를 쉽게 Ajax화할 수 있다고 하더라도 구현하는 데는 신중을 기해야 한다. 두 가지 컴포넌트는 전적으로 Facelet 마크업과 Javascript를 사용하여 구현되며 대략 마크업 100행과 Javascript 50행으로 구성된다. 비교하기 위해 이 기사에서는 다음과 같은 세 가지 과정을 통해 컴포넌트를 개발하는 과정을 탐구한다.

  • 클라이언트에서 끌어서 놓기 구현
  • 놓기가 수행될 때 Ajax 호출 추가
  • 조건부 끌기 지원

클라이언트에서의 끌어서 놓기

여기에서는 먼저, 클라이언트에 집중한다. 사용자가 놓기 대상에 링크를 놓으면 그림 4에서와 같이 URL 다음에 기사 제목이 표시된 경보가 나타난다.


그림 4. 놓기 대상에 놓여진 링크가 표시된 경보
놓기 대상에 놓여진 링크가 표시된 경보

그림 4에 표시된 것과 같은 끌어서 놓기를 구현하는 방법을 설명하기 위해 다음과 같은 아티팩트를 살펴본다.

  • 끌기 소스
  • 끌기 소스 컴포넌트
  • 놓기 대상
  • 놓기 대상 컴포넌트
  • 놓기 대상 컴포넌트의 Javascript

끌기 소스

애플리케이션 중앙에 있는 각 링크(그림 5에서 강조된 부분)는 끌기 소스이다.


그림 5. 끌기 소스
끌기 소스

목록 2에는 끌기 소스의 마크업과 Javascript가 표시되어 있다.


목록 2. 끌기 소스의 마크업과 Javascript

<ui:composition xmlns="http://www.w3.org/1999/xhtml"
   xmlns:h="http://java.sun.com/jsf/html"
   xmlns:ui="http://java.sun.com/jsf/facelets"
   xmlns:h5="http://java.sun.com/jsf/composite/html5"
   xmlns:places="http://java.sun.com/jsf/composite/places">

   <script>
      // event.target is one of the h5:drag elements generated by ui:repeat below
       function dragStart(event) {
          var linkref = event.target.firstElementChild.firstElementChild; // anchor
          var link = linkref.href;
          var title = linkref.textContent;

          event.dataTransfer.setData('text', title + " | " + link + " ");
      }
    </script>


   <h:panelGrid id="items" columns="1" >
     <ui:repeat value="#{rssFeed.items}" var="item">


      <h5:drag ondragstart="dragStart(event)">
        <p><a href="#{item.link}">#{item.title}</a> <br /></p>
      </h5:drag>

     </ui:repeat>
   </h:panelGrid>

</ui:composition>

목록 2의 마크업에서는 끌기가 시작될 때 JSF에서 호출하는 Javascript 함수를 <h5:drag> 컴포넌트를 사용하여 지정한다. 브라우저는 이벤트를 해당 Javascript 함수에게 전달한다. 이 이벤트의 대상은 <h5:drag> 컴포넌트이다. 이 함수는 <h5:drag> 요소에서 앵커 요소로 드릴 다운하여 링크에 대한 참조와 텍스트를 얻는다. 또한, 이러한 정보를 text 데이터 전송 유형과 연관된 문자열에 임베드한다. 이후에는 놓기가 승인될 때 HTML5 끌어서 놓기 시스템이 이 문자열을 놓기 대상으로 전송한다.

끌기 소스 컴포넌트

<h5:drag> 컴포넌트(목록 3)는 다음과 같이 간단하게 구현된다.


목록 3. <h5:drag> 컴포넌트

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml"
   xmlns:h="http://java.sun.com/jsf/html"
   xmlns:composite="http://java.sun.com/jsf/composite">

   <composite:interface>
     <composite:attribute name="ondragstart"/>
   </composite:interface>
   
   <composite:implementation>
     <div draggable="true" 
         ondragstart="#{cc.attrs.ondragstart}">
        <composite:insertChildren />
     </div>   
   </composite:implementation>
</html>

<h5:drag> 컴포넌트는 끌기 가능한 DIV를 작성하며 여기에 있는 ondragstart Javascript는 목록 2에서 페이지 작성자가 지정한 값이다. 또한, 이 컴포넌트는 <composite:insertChildren>을 사용하여 <h5:drag> 태그 본문에 있는 모든 마크업을 삽입한다. 목록 2에서는 이 마크업이 링크의 앵커가 된다.

놓기 대상

그림 6에 표시된 것과 같은 놓기 대상은 애플리케이션 오른쪽 메뉴에 있다.


그림 6. 놓기 대상
놓기 대상

목록 4에는 구현된 놓기 대상이 표시되어 있다.


목록 4. 놓기 대상

<ui:composition xmlns="http://www.w3.org/1999/xhtml"
   xmlns:ui="http://java.sun.com/jsf/facelets"
   xmlns:f="http://java.sun.com/jsf/core"
   xmlns:h="http://java.sun.com/jsf/html"
   xmlns:util="http://java.sun.com/jsf/composite/util"
   xmlns:fn="http://java.sun.com/jsp/jstl/functions"
   xmlns:h5="http://java.sun.com/jsf/composite/html5">

   <script>
        function drop(event) { alert(event.dataTransfer.getData("text")); }
    </script>

   <h5:drop id="dropzone"
      ondrop="drop(event)">

      <div class="welcomeImage">
          <h:graphicImage id="welcomeImage" 
             library="images" name="cloudy.gif"/>
      </div>

   </h5:drop>
</ui:composition>

놓기 대상은 <h5:drop> 컴포넌트로 구성되며 DIV로 둘러싸인 클라우드 이미지를 포함하고 있다. <h5:drop> 컴포넌트의 ondrop 속성은 끌기 소스에서 전송된 문자열이 포함된 경보를 표시하는 Javascript 함수를 참조한다.

놓기 대상 컴포넌트

목록 5에는 구현된 <h5:drop> 컴포넌트가 표시되어 있다.


목록 5. <h5:drop> 컴포넌트


<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml"
   xmlns:f="http://java.sun.com/jsf/core"
   xmlns:h="http://java.sun.com/jsf/html"
   xmlns:composite="http://java.sun.com/jsf/composite">

   <composite:interface>
      <composite:attribute name="ondragenter"/>
      <composite:attribute name="ondragover"/>
      <composite:attribute name="ondragleave"/>
      <composite:attribute name="ondrop"/>
   </composite:interface>
   

   <composite:implementation>
      <div id="#{cc.id}" ondragenter="#{cc.attrs.ondragenter}"
                         ondrop="#{cc.attrs.ondrop}"
                         ondragover="#{cc.attrs.ondragover}"
                         ondragleave="#{cc.attrs.ondragleave}">
                         
        <composite:insertChildren />
          
      </div>
      
      <script> html5.jsf.init("#{cc.id}"); </script>
   </composite:implementation>
</html>

<h5:drag>와 마찬가지로 <h5:drop> 컴포넌트는 DIV를 작성한 다음, <h5:drop> 태그의 본문에 있는 마크업을 이 DIV에 삽입한다.

<h5:drop> 컴포넌트는 컴포넌트가 작성될 때 html5.jsf.init() 함수를 호출한다. 이 함수는 drag-enter와 drag-over 이벤트 핸들러를 추가하여 이 컴포넌트의 DIV를 초기화한다. 이러한 이벤트 핸들러는 목록 6에 구현되어 있다.


목록 6. <h5:drop> 컴포넌트의 Javascript

if (!html5)
   var html5 = {}
if (!html5.jsf) {
   html5.jsf = {
      init : function(ccid) {
         var dropzone = $(ccid);

         dropzone.addEventListener("dragenter", function(event) {
            event.preventDefault();
         }, false);

         dropzone.addEventListener("dragover", function(event) {
            event.preventDefault();
         }, false);
      }
   };
}

목록 6에 있는 Javascript(충돌을 피하기 위해 네임스페이스를 지정함)는 브라우저가 drag-enter와 drag-over 이벤트에 기본적으로 반응하지 않도록 한다. 브라우저에서는 기본적으로 이러한 이벤트가 취소되기 때문에 목록 6에서는 Javascript를 이용하여 부라우저에서 놓기 동작을 가능하게 한다. 이론의 여지가 있지만, 이러한 preventDefault() 함수를 호출하기는 다소 어렵다. 그러나 그런 만큼 재사용 가능한 컴포넌트로 캡슐화하는 것도 좋은 방법이다.

이 Javascript는 무조건 브라우저가 drag-enter와 drag-over 이벤트를 취소하지 않도록 하기 때문에 브라우저에서는 모든 놓기가 무조건 승인된다. 이렇게 되는 것은 현실적으로 적합하지 않으므로 조건부 끌기로 이러한 문제점을 처리한다. 다음에는 Ajax를 놓기 대상 컴포넌트에 추가하는 방법을 살펴본다.


Ajax를 추가하고 페이로드를 서버로 전송

클라이언트에서의 끌어서 놓기 섹션에서는 링크의 제목과 URL이 표시된 경보를 화면에 표시함으로써, 놓은 링크를 전적으로 클라이언트에서 처리했다(그림 4 참조). 끌어서 놓기를 제공하는 대부분의 중요한 JSF 애플리케이션과 Feeds 애플리케이션에서는 놓기를 하면 페이로드가 서버로 이동하여 서버측 데이터에 통합된다. 이러한 요구사항 때문에 이 섹션에서는 Ajax를 <h5:drop> 컴포넌트에 추가한다. 따라서 이 컴포넌트는 놓기가 수행될 때 자동으로 Ajax를 호출하여 페이로드를 함께 전송한다.

사용자가 오른쪽 메뉴에 링크를 놓을 때마다 <h5:drop> 컴포넌트는 Ajax를 호출하여 이 링크를 서버에 저장된 애플리케이션의 링크 목록에 추가한다. Ajax 호출이 리턴되면 JSF는 놓기 대상을 업데이트하여 새로 추가된 링크를 반영한다.

목록 7에는 업데이트된 놓기 대상이 표시되어 있다.


목록 7. 놓기 대상, 두 번째

<ui:composition xmlns="http://www.w3.org/1999/xhtml"
   xmlns:ui="http://java.sun.com/jsf/facelets"
   xmlns:h="http://java.sun.com/jsf/html"
   xmlns:fn="http://java.sun.com/jsp/jstl/functions"
   xmlns:h5="http://java.sun.com/jsf/composite/html5">

   <script>
        function dragenter(event) { /* Implement as desired */ }
        function dragleave(event) { /* Implement as desired */ }
        function dragover(event)  { /* Implement as desired */ }
        function drop(event)      { /* Implement as desired */ }
    </script>

   <h5:drop id="dropzone" payload="#{dragDrop.payload}" 
                           render="@this"
      ondragover="dragover(event)"
      ondragenter="dragenter(event)" 
      ondragleave="dragleave(event)"
      ondrop="drop(event)">

      <div class="welcomeImage">
          <h:graphicImage id="welcomeImage" 
             library="images" name="cloudy.gif"/>
      </div>
         
      <br />

      <div class="savedItems">
           <ui:repeat value="#{rssFeed.savedItems}" var="item">
             <div class="savedLink">
              <a href="#{item.link}">
                 #{ fn:substring(item.title, 0, 25) } ...
               </a>
               
               <br/>
           </div>
        </ui:repeat>      
      </div>

   </h5:drop>
</ui:composition>

이번 놓기 대상 버전에서는 끌어서 놓기 페이로드를 빈 특성(#{dragDrop.payload})에 연결했다. 그리고 놓기 동작에 의해 Ajax 호출이 시작될 때, @this(놓기 대상 자체)를 렌더링할 놓기 대상이 리턴된다고 설명했다.

목록 8에는 구현된 <h5:drop> 컴포넌트를 업데이트한 코드가 표시되어 있다.


목록 8. <h5:drop> 컴포넌트 두 번째
<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml"
   xmlns:f="http://java.sun.com/jsf/core"
   xmlns:h="http://java.sun.com/jsf/html"
   xmlns:composite="http://java.sun.com/jsf/composite">

   <composite:interface>
      <composite:attribute name="ondragenter"/>
      <composite:attribute name="ondragover"/>
      <composite:attribute name="ondragleave"/>
      <composite:attribute name="ondrop"/>
      <composite:attribute name="render"/>
      <composite:attribute name="payload"/>
      <composite:attribute name="payloadType"/>
   </composite:interface>
   
   <composite:implementation>
      <h:outputScript library="javax.faces" name="jsf.js" target="head" />
      <h:outputScript library="html5" name="drop.js" target="head" />
        
      <div id="#{cc.id}" ondragenter="#{cc.attrs.ondragenter}"
                         ondrop="#{cc.attrs.ondrop}"
                         ondragover="#{cc.attrs.ondragover}"
                         ondragleave="#{cc.attrs.ondragleave}">
                         

        <composite:insertChildren />
          
        <h:form id="form">
          <h:inputText id="payload" 
                     value="#{cc.attrs.payload}" 
                     style="display: none"/>   
        </h:form>
          
        </div>
      
        <script> html5.jsf.init("#{cc.id}", 
                                "#{cc.attrs.payloadType}", 
                                "#{cc.attrs.render}"); </script>
   </composite:implementation>
</html>

업데이트된 놓기 대상은 다음과 같은 과정을 수반한다.

  • 놓기가 수행될 때 Ajax 호출을 한다.
  • Ajax 호출 과정에서 페이로드를 서버에서 사용 가능하게 한다.

목록 9에 표시된 <h5:drop> 컴포넌트의 Javascript에서 JSF의 jsf.ajax.request() Javascript 함수를 사용하여 Ajax를 호출한다.


목록 9. <h5:drop> 컴포넌트의 Javascript 두 번째
if (!html5)
   var html5 = {}
if (!html5.jsf) {
   html5.jsf = {
      init : function(ccid, payloadType, renderIds) {
         var dropzone = $(ccid);

         dropzone.payloadInput = $(ccid + ":form:payload");

         dropzone.addEventListener("drop", function(event) {
            if (payloadType == "")
               payloadType = "text";

            if (renderIds == "" || renderIds == "@this")
               renderIds = ccid;

            dropzone.payloadInput.value = event.dataTransfer
                  .getData(payloadType);

            jsf.ajax.request(dropzone.payloadInput, event, {
               render : renderIds,
               onevent : function(data) {
                   if (data.status == "success")
                      html5.jsf.init(ccid, payloadType, renderIds);
                }
            });
         }, false);

         dropzone.addEventListener("dragenter", function(event) {
            event.preventDefault();
         }, false);

         dropzone.addEventListener("dragover", function(event) {
            event.preventDefault();
         }, false);
      }
   };
}

목록 7에서 다음과 같이 페이로드를 지정했다는 점을 상기하자.

<h5:drop...payload="#{dragDrop.payload}">

여기서는 목록 8에 있는 <h5:drop> 컴포넌트의 보이지 않는 입력 텍스트를 통해 페이로드를 사용 가능하게 한다. 클라이언트와 서버 간에 값을 주고 받는 방법이 필요하며 JSF는 이미 <h:inputText>를 사용하여 이러한 기능을 제공한다. 따라서 여기에서는 보이지 않는 입력 텍스트를 놓기 대상에 추가하고 목록 9에 있는 Javascript에서 볼 수 있는 바와 같이 보이지 않는 입력의 값을 놓기가 수행되어 Ajax가 호출되기 바로 전에 설정한다.

Ajax가 호출되기 전에 입력의 값을 설정하므로 이제 끌어서 놓기 페이로드는 입력의 값에 저장되며 Ajax를 호출하는 과정에서 서버에서 사용 가능하게 된다. 입력 텍스트는 빈 특성에 연결되기 때문에 Ajax를 호출하는 과정에서 JSF가 페이로드를 빈 특성의 세터 메소드에 전달하게 된다.

보이지 않는 텍스트 입력의 값과 마찬가지로 이 값도 목록 8에 있는 <h5:drop> 컴포넌트가 사용한다.

<h:inputText id="payload" value="#{cc.attrs.payload}" style="display: none"/>

목록 9에서는 보이지 않는 텍스트 입력을 다음과 같이 Ajax 요청의 소스로 지정한다.

jsf.ajax.request(dropzone.payloadInput, event, {...});

보이지 않는 텍스트 입력이 Ajax 요청의 소스가 되기 때문에 JSF가 해당 서버에서 이 입력을 처리한다. 따라서 JSF가 텍스트 입력의 연관된 특성-세터 메소드, 즉 목록 10에 있는 DragDrop.setPayload() 메소드를 호출하게 된다.


목록 10. payload 특성의 구현
package com.clarity;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.StringTokenizer;

import javax.enterprise.context.SessionScoped;
import javax.inject.Inject;
import javax.inject.Named;

import org.gnu.stealthp.rsslib.RSSItem;

@Named
@SessionScoped
public class DragDrop implements Serializable {
   @Inject private RSSFeed rssFeed;

   public DragDrop() {
   }
   
   public String getPayload() {
      // JSF requires both getters and setters for input properties
      return "";
   }
   
   public void setPayload(String payload) {
      // creates a new saved item, based on the payload. Payload 
      // was set in the drop event listener for the h5:drop component 
      // in /sections/feeds/menuLeft.xhtml
      StringTokenizer st = new StringTokenizer(payload);
      RSSItem item = new RSSItem();
      
      item.setTitle(st.nextToken("|"));
      st.nextToken(" ");
      item.setLink(st.nextToken(" "));
      
      rssFeed.getSavedItems().add(item);
   }
}

JSF는 끌어서 놓기 페이로드를 DragDrop.setPayload() 메소드에 전달한다. 이 페이로드를 기반으로 DragDrop.setPayload() 메소드는 RSS 항목을 새로 작성하여 rssFeed의 저장된 항목 목록에 추가한다. 또한, JSF는 입력값에 맞는 세터와 게터를 모두 필요로 하기 때문에 페이로드 특성에 맞는 게터 메소드를 삽입한다.

복합 컴포넌트를 사용하면 기존의 컴포넌트 기능(이 경우에는 클라이언트와 서버 간에 데이터를 전송하는 <h:inputText>의 기능)을 재사용하기가 쉽다. 여기에서는 <h5:drop> 컴포넌트에 있는 보이지 않는 텍스트 입력을 사용하여 클라이언트에서 서버로 데이터를 전송한다. 이 과정은 단순히 텍스트 입력을 복합 컴포넌트에 추가하고 이 입력값을 Ajax를 호출하기 전에 설정함으로써 이루어진다.

이제 놓기에 대한 응답으로 Ajax를 호출하여 전송된 데이터를 클라이언트에서 서버로 전달하는 매우 복잡한 놓기 대상(끌기 소스는 소개한 이후로 변경한 적이 없다.)을 완성했다. 이 끌기 대상에서는 페이지 작성자가 Ajax 호출이 리턴될 때 렌더링하고자 하는 컴포넌트를 지정할 수 있다. 그러나 필자의 끌어서 놓기 컴포넌트에는 여전히 한 가지 기능(조건부 끌기)이 부족하다.


조건부 끌기

목록 1에서는 Feeds 애플리케이션의 저장된 링크 목록을 연결 목록으로 구현했다. 따라서 사용자가 같은 기사를 중복해서 추가할 수 있었다. 그림 7에서 표시된 것과 같이 이러한 작동은 허용하지 말아야 한다. 끌은 제목 위에 있는 커서가 놓기가 성공적으로 수행된 그림 2에서와 같이 플러스 부호로 변하지 않는다는 점에 주목한다.


그림 7. 놓기가 허용되지 않음(커서가 복사를 나타내지 않음)
놓기가 허용되지 않음(커서가 복사를 나타내지 않음)

중복해서 놓기를 허용하지 않으려면 목록 11에 표시된 것과 같이 놓기를 조건부로 승인하도록 <h5:drop> 컴포넌트의 drag-enter와 drag-over 이벤트 핸들러를 변경해야 한다.


목록 11. <h5:drop> 컴포넌트의 Javascript, 세 번째
if (!html5)
   var html5 = {}
if (!html5.jsf) {
   html5.jsf = {
      init : function(ccid, payloadType, renderIds) {
         var dropzone = $(ccid);


         if (dropzone.serverPayload) // already initialized
            return;
         
         dropzone.payloadInput = $(ccid + ":form:payload");
         dropzone.acceptDrop = false;
         dropzone.serverPayload = function() {
            return dropzone.payloadInput.value;
         };

         dropzone.addEventListener("drop", function(event) {
            if (payloadType == "")
               payloadType = "text";

            if (renderIds == "" || renderIds == "@this")
               renderIds = ccid;

            dropzone.payloadInput.value = event.dataTransfer
                  .getData(payloadType);
            
            jsf.ajax.request(dropzone.payloadInput, event, {
               render: renderIds
               onevent : function(data) {
                   if (data.status == "success")
                      html5.jsf.init(ccid, payloadType, renderIds);
                }
            });            
         }, false);

         dropzone.addEventListener("dragenter", function(event) {
            if (dropzone.acceptDrop)
               event.preventDefault();
         }, false);

         dropzone.addEventListener("dragover", function(event) {
            if (dropzone.acceptDrop)
               event.preventDefault();
         }, false);
      }
   };
   }

@Inject

목록 12에 있는 @Inject 어노테이션에 주목하자. 이 어노테이션은 rssFeed 관리 빈에 액세스할 수 있는 기능을 제공한다. JSF 애플리케이션은 Java 코드의 관리 빈에 자주 액세스해야 한다. JSF와 JSP 표현식 언어 API를 사용하여 이러한 관리 빈에 액세스할 수 있다. 그렇지 않고 단순히 @Inject 어노테이션을 사용할 수도 있다.

Ajax를 추가하고 페이로드를 서버로 전송 섹션에서는 끌어서 놓기 페이로드를 클라이언트에서 서버로 전송하는 데 집중했다. 놓기를 조건부로 승인하려면 서버에서 다시 클라이언트로 정보를 반대로 전송해야 한다. 이 경우에는 저장된 링크 목록에 액세스해야 하므로 링크가 중복되었는지 판별할 수 있다. 따라서 필수 데이터를 서버에서 클라이언트로 다시 전송하려면 serverPayload 변수를 dropzone에 추가해야 한다.

저장된 링크 목록을 다시 서버로 전송하기 위해 여기에서는 목록 12에서와 같이 DragDrop.getPayload() 메소드를 구현하여 저장된 각 링크의 제목이 포함된 문자열을 놓기 대상의 보이지 않는 텍스트 입력에 저장한다.


목록 12. payload 특성의 게터 메소드
package com.clarity;

// imports omitted. 목록 10을 참조한다.

@Named
@SessionScoped
public class DragDrop implements Serializable {
   @Inject private RSSFeed rssFeed;

   public DragDrop() {
   }
   
   public String getPayload() {
      // sends a string that is a concatenation of the saved 
      // item's titles, back to the client
      LinkedList<RSSItem> savedItems = rssFeed.getSavedItems();
      Iterator<RSSItem> it = savedItems.iterator();
      String s = "";
      
      while (it.hasNext()) {
         RSSItem item = it.next();
         s += item.getTitle() + " | ";
      }
      return s;
   }
   
   public void setPayload(String payload) {
      // creates a new saved item, based on the payload. Payload 
      // was set in the drop event listener for the h5:drop component 
      // in /sections/feeds/menuLeft.xhtml
      StringTokenizer st = new StringTokenizer(payload);
      RSSItem item = new RSSItem();
      
      item.setTitle(st.nextToken("|"));
      st.nextToken(" ");
      item.setLink(st.nextToken(" "));
      
      rssFeed.getSavedItems().add(item);
   }
}

다음에 끌기가 시작되면 애플리케이션의 끌기 소스는 목록 13에서와 같이 서버에서 페이로드(보이지 않는 텍스트 입력값)를 검사하여 끌은 링크가 중복된 것인지 확인한다.


목록 13. 끌기 소스, 두 번째
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
   xmlns:h="http://java.sun.com/jsf/html"
   xmlns:ui="http://java.sun.com/jsf/facelets"
   xmlns:h5="http://java.sun.com/jsf/composite/html5"
   xmlns:places="http://java.sun.com/jsf/composite/places">

   <script>
      // event.target is one of the h5:drag elements generated by ui:repeat below
       function dragStart(event) {
          var linkref = event.target.firstElementChild.firstElementChild; // anchor
          var link = linkref.href;
          var title = linkref.textContent;

          var dropzone = $("dropzone"); // h5:drop in dropZone.xhtml
          dropzone.acceptDrop = true;          
                    
          var serverPayload = dropzone.serverPayload();
          if (serverPayload.indexOf(title) != -1)
           dropzone.acceptDrop = false; // link already present
                       
          event.dataTransfer.setData('text', title + " | " + link + " ");
      }
    </script>

   <h:panelGrid columns="1" id="items">
     <ui:repeat value="#{rssFeed.items}" var="item">

       <h5:drag ondragstart="dragStart(event)">
        <p>
          <a href="#{item.link}">#{item.title}</a> <br />
        </p>
      </h5:drag>

      </ui:repeat>
   </h:panelGrid>

</ui:composition>

끌은 링크가 중복된 것이면 끌기 소스의 Javascript가 놓기 대상의 acceptDrop 속성을 false로 설정하며 놓기는 그 후에 놓기 대상에 의해 취소된다.


결론

이 기사에서는 HTML5 끌어서 놓기를 캡슐화하는 복합 컴포넌트를 JSF 2를 사용하여 구현하는 방법을 설명했다. 복합 컴포넌트를 이용하면 Java 코드를 작성하거나 구성 작업을 수행하지 않고도 재사용 가능한 컴포넌트를 구현할 수 있기 때문에 복합 컴포넌트는 JSF 개발자의 도구 상자에 추가할 만한 강력한 도구이다. 또한, 이 기사에서는 JSF의 내장 <h:inputText> 요소와 같은 기존 컴포넌트를 재사용하는 방법과 자신의 복합 컴포넌트를 구현할 때 수행해야 하는 작업량을 줄일 수 있는 방법을 설명했다.

독자가 소수의 복합 컴포넌트를 개발했으면 이러한 복합 컴포넌트를 JAR 파일로 함께 그룹화하여 다른 개발자들이 사용할 수 있게 하는 것이 현명하다. JAR를 실행하여 WEB-INF 대신 META-INF 디렉토리와 함께 이러한 복합 컴포넌트를 그룹화한다.

JSF2 fu 시리즈의 다음 기사에서는 이 시리즈에서 다룬 적이 있고 일부 JSF 2 우수 사례에서 권장한 내용을 다시 살펴볼 예정이다.



다운로드 하십시오

설명이름크기다운로드 방식
Sample code for this articlej-jsf2fu-1110.zip5.39MBHTTP

다운로드 방식에 대한 정보


참고자료

교육

제품 및 기술 얻기

  • JSF: JSF 2.0을 다운로드할 수 있다.

  • RSSLib4J: RSSLib4J를 다운로드하자.

토론

  • My developerWorks 커뮤니티에 참여하자. 개발자가 이끌고 있는 블로그, 포럼, 그룹 및 Wiki를 살펴보면서 다른 developerWorks 사용자와 의견을 나눌 수 있다.

필자소개

David Geary

David Geary, là tác giả, diễn giả và nhà tư vấn, là chủ tịch của Clarity Training, Inc., nơi ông dạy cho các nhà phát triển cách triển khai thực hiện các ứng dụng Web bằng cách sử dụng JSF và Bộ công cụ Web của Google (Google Web Toolkit-GWT). Ông đã ở trong nhóm chuyên gia JSTL 1.0 và JSF 1.0/2.0, đồng tác giả của kỳ thi lấy chứng chỉ nhà phát triển Web của Sun và đã đóng góp vào các dự án mã nguồn mở, bao gồm Apache Struts và Apache Shale. Cuốn Graphic Java Swing của David đã là một trong những cuốn sách Java bán chạy nhất mọi thời đại và Core JSF (viết cùng với Cay Horstman), là cuốn sách JSF bán chạy nhất. David thuyết trình thường xuyên tại các hội nghị và các nhóm người dùng. Ông đã thường xuyên tham gia các chuyến đi NFJS từ năm 2003, đã dạy các khóa học tại Đại học Java và đã hai lần được bình chọn là một ngôi sao rock của JavaOne.

잘못된 도움말 신고

부정사용 신고

감사합니다. 이 항목은 운영자가 관심을 표시했습니다.


잘못된 도움말 신고

부정사용 신고

제출실패 신고. 나중에 다시 실행해주세요.


디벨로퍼웍스 로그인


IBM ID가 필요하세요?
IBM ID를 잊으셨습니까?


비밀번호를 잊으셨습니까?
비밀번호 변경

developerWorks 이용 약관에 동의하시는 경우 제출을 클릭하십시오. 이용 약관.

 


developerWorks에 처음 로그인하면 developerWorks프로파일이 생성됩니다.귀하의 프로파일에서 동의하신 내용이 공개되지만 이 사항은 언제든지 변경 가능합니다. 귀하의 성명(숨김으로 체크되어 있어도 표시됩니다)과 디스플레이 이름은 게시한 컨텐츠나 사이트 엑세스시 표시됩니다.

화면상에 보여지는 닉네임을 정하세요.

처음 developerWorks에 로그인할 때 프로파일이 작성되므로, 이를 위해 디스플레이 이름을 선택해야 합니다. 선택하신 디스플레이 이름은 developerWorks에 게시한 컨텐츠에 표시됩니다.

3글자 이상 31글자 이하의 길이로 사용 가능합니다. dW커뮤니티 내에서는 보안상 이메일주소를 제외한 다른 이름을 지정하셔야 합니다.

3개의 &이나 대쉬를 포함해주시고 31글자내로 제한해주세요.


developerWorks 이용 약관에 동의하시는 경우 제출을 클릭하십시오. 이용 약관.

 


아티클 순위

의견

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=20
Zone=자바, 웹 개발
ArticleID=646547
ArticleTitle=JSF 2 fu: HTML5 복합 컴포넌트, Part 2
publish-date=11232010
author1-email=david.mark.geary@gmail.com
author1-email-cc=

태그

Help
검색 필드를 사용하여 My developerWorks 내에서 해당 태그가 사용된 모든 종류의 컨텐츠를 검색하십시오.

태그를 더 많이 보거나 적게 보기 위해 슬라이더 막대를 사용하십시오.

인기 태그는 특정 컨텐츠 존(예를 들어, 자바, 리눅스, WebSphere)의 최고 인기 태그를 보여줍니다.

내 태그는 특정 컨텐츠 존(예를 들어, 자바, 리눅스, WebSphere)의 귀하의 태그를 보여줍니다.

검색 필드를 사용하여 My developerWorks 내에서 해당 태그가 사용된 모든 종류의 컨텐츠를 검색하십시오. 인기 태그는 특정 컨텐츠 존(예를 들어, 자바, 리눅스, WebSphere)의 최고 인기 태그를 보여줍니다. 내 태그는 특정 컨텐츠 존(예를 들어, 자바, 리눅스, WebSphere)의 귀하의 태그를 보여줍니다.