JSF 2 fu: HTML5 composite components, Part 2

Implementing drag and drop

In this JSF 2 fu installment, series author David Geary continues to demonstrate the power of combining Java™Server Faces (JSF) 2 technology with HTML5. This time you'll see how to implement JSF composite components that encapsulate HTML5 drag and drop.

David Geary, President, Clarity Training, Inc.

David GearyAuthor, speaker, and consultant David Geary is the president of Clarity Training, Inc., where he teaches developers to implement Web applications using JSF and Google Web Toolkit (GWT). He was on the JSTL 1.0 and JSF 1.0/2.0 Expert Groups, co-authored Sun's Web Developer Certification Exam, and has contributed to open source projects, including Apache Struts and Apache Shale. David's Graphic Java Swing was one of the best-selling Java books of all time, and Core JSF (co-written with Cay Horstman), is the best-selling JSF book. David also speaks regularly at conferences and user groups. He has been a regular on the NFJS tour since 2003, is a three-time Java University instructor, and a three-time JavaOne Rock Star.



23 November 2010

Also available in Chinese Japanese

About this series

The JSF 2 fu series, a follow-on to David Geary's three-article introduction of the same name, will help you develop and hone your JSF 2 framework skills. The current series dives deeper into the framework and its surrounding ecosystem. And it takes a peek outside the box by showing how some Java EE technologies, such as Contexts and Dependency Injection, integrate with JSF.

JSF 2 fu's preceding installment, a prerequisite to this article, introduced you to HTML5's features and showed how to build a JSF composite component that makes it easy to use HTML5 canvases. In this article, I work through the implementation of two JSF composite components — <h5:drag> and <h5:drop>— that leverage HTML5's event-based, drag-and-drop mechanism (see Resources).

The drag and drop composite components have five significant features:

  • Ease of use
  • Conditional dragging
  • Ajax
  • Partial rendering
  • Payload

At the most fundamental level, both <h5:drag> and <h5:drop> encapsulate some of the vagaries of HTML5 drag and drop to enhance ease of use. For example, browsers reject drag and drop by default, so in your drag-enter and drag-over event handlers, you must cancel the browser's default reaction by canceling the event. This nonintuitive pitfall is dealt with in the <h5:drag> component, which lets page authors concentrate on more meaningful matters.

The <h5:drag> and <h5:drop> components support conditional dragging. The page author can accept or reject drops based on the data being transferred and the drop target in question.

Running the sample code

The code for this series is based on JSF 2 running in an enterprise container, such as GlassFish or Resin. See the first series installment, "JSF 2 fu: Ajax components" for a step-by-step tutorial on installing and running the code for this series with GlassFish. See Download to get the sample code for this article.

In JSF applications, most of the data that users manipulate is stored on the server, typically as managed beans. For this reason, the <h5:drop> component makes an Ajax call when it accepts a drop. Page authors can specify which components JSF will render when the Ajax call returns.

The <h5:drag> and <h5:drop> components also support attaching some data — commonly referred to as a payload — to a drag-and-drop gesture. Page authors specify a bean property that serves as the payload. During the Ajax call, initiated by the <h5:drop> component when a drop occurs, JSF invokes the setter method for the payload bean property. JSF treats drag-and drop-payloads, therefore, just like values for <h:inputText> elements.

Using drag sources and drop targets

<h5:drag> and <h5:drop> represent HTML5 drag sources and drop targets, respectively. In a JSF application that uses these components, you use drag sources like this:

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

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

   ...

</h5:drag>

The page author can put some components, or HTML elements, in the <h5:drag> component, and set up the transfer data in the ondragstart function, as in the preceding markup.

You use drop targets like this:

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

   ...

</h5:drop>

As with drag sources, the page author can put some components, or HTML elements, in the <h5:drop> component. The page author also specifies a bean property for the drag-and-drop payload, and specifies which components JSF should render when the Ajax call, executed after a drop, returns from the server.

Page authors can also optionally intervene in a drag-and-drop gesture with some JavaScript:

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

   ...

</h5:drop>

In the preceding markup, the dragover(), dragenter(), dragleave(), and drop() functions (not shown) are implemented by the page author.

Now that you know where this article is headed, I'll discuss the drag-and-drop use case for the article: the Feeds application.


The Feeds application

The Feeds application, shown in Figure 1, is an RSS feed reader. The left menu displays a list of RSS feeds and lets users add feeds to the list. The middle of the page shows the article links for the current feed. When the user clicks on an article link, the application loads the associated article in the browser, and the user can subsequently hit the Back button to get back to the application.

Figure 1. The Feeds application
Screenshot of the Feeds application open in a browser

The list of articles for any given RSS feed updates regularly, so busy users can save links for future reading by dragging article links from the center of the application to the right menu, as shown in Figure 2:

Figure 2. Drag and drop in the Feeds application
Drag and drop in the Feeds application

The application has a managed bean, shown in Listing 1, that reads an RSS feed and provides a subsequent list of RSS items:

Listing 1. Retrieving and parsing RSS feeds
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; }
}

The RSSFeed class uses RSSLib4J, which makes it easy to retrieve and parse an RSS feed (see Resources). By virtue of the @Named and @SessionScoped attributes in Listing 1, the application has a session-scoped managed bean named rssFeed that is an instance of RSSFeed.

The links in the application's left menu all have actions that invoke rssFeed.fetch(). For example, the Apple link in the left menu is implemented like this:

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

When the user clicks on the link, JSF invokes the rssFeed managed bean's fetch() method and subsequently reloads the list of links shown in the middle of the application:

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

The application also displays the saved links in the right menu:

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

Now that you've seen how the Feeds application retrieves and displays items from RSS feeds, I'll turn my attention to this article's main event.


The <h5:drag> and <h5:drop> components

The <h5:drag> and <h5:drop> components are implemented in three files, one each (drag.xhtml and drop.xhtml) for the components, and another (drop.js) for the <h5:drop> component's JavaScript, as shown in Figure 3:

Figure 3. Files for the <h5:drop> and <h5:drag> components
Screenshot of the folder hierarchy for the drag and drop components

Although the components make Ajaxified drag and drop easy, their implementations are modest. Both components are implemented entirely with facelets markup and JavaScript: roughly 100 lines of markup, and 50 lines of JavaScript. For the sake of illustration, I'll explore their development in three iterations:

  • Implement drag and drop on the client
  • Add Ajax calls when drops occur
  • Support conditional dragging

Drag and drop on the client

To begin, I'll concentrate on the client. When the user drops a link in the drop target, I simply show an alert that displays the title of the article followed by its URL, as shown in Figure 4:

Figure 4. Alert displaying a link dropped on the drop target
Alert displaying a link dropped on the drop target

To illustrate how to implement drag and drop as shown in Figure 4, I discuss the following artifacts:

  • Drag source
  • Drag source component
  • Drop target
  • Drop target component
  • Drop target component's JavaScript

The drag source

Each link in the center of the application, highlighted in Figure 5, is a drag source:

Figure 5. The drag source
The drag source

Listing 2 shows the markup and JavaScript for the drag source:

Listing 2. Markup and JavaScript for the drag source
<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>

Listing 2's markup uses the <h5:drag> component, specifying a JavaScript function that JSF calls when a drag starts. The browser passes an event to that JavaScript function. The target of that event is the <h5:drag> component. The function drills down from the <h5:drag> element to the anchor element and gets the text and reference for the link. It embeds that information in a string associated with the text data-transfer type. From there on out, the HTML5 drag-and-drop system will transfer that string to the drop target when a drop is accepted.

The drag source component

The implementation of the <h5:drag> component, shown in Listing 3, is simple:

Listing 3. The <h5:drag> component
<!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>

The <h5:drag> component creates a draggable DIV whose ondragstart JavaScript is the value specified by the page author in Listing 2. The component also uses <composite:insertChildren> to insert whatever markup is in the body of the <h5:drag> tag. In Listing 2, that markup is the link's anchor.

The drop target

The drop target, as shown in Figure 6, is in the application's right menu:

Figure 6. The drop target
The drop target

Listing 4 shows the implementation of the drop target:

Listing 4. The drop target
<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>

The drop target consists of an <h5:drop> component, which contains the cloud image, wrapped in a DIV. The ondrop attribute of the <h5:drop> component refers to a JavaScript function that shows an alert containing the string transferred from the drag source.

The drop target component

Listing 5 shows the implementation of the <h5:drop> component:

Listing 5. The <h5:drop> component
<!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>

The <h5:drop> component, like <h5:drag>, creates a DIV and inserts the markup in the body of the <h5:drop> tag into that DIV.

The <h5:drop> component invokes html5.jsf.init() when the component is created. That function initializes the component's DIV by adding drag-enter and drag-over event handlers. Those event handlers are implemented in Listing 6:

Listing 6. The <h5:drop> component's 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);
      }
   };
}

The JavaScript in Listing 6 (which I have namespaced to avoid collisions) prevents the browser from its default reaction to drag-enter and drag-over events. Recall that by default, the browser cancels those events, so the JavaScript in Listing 6 prevents the browser from rejecting the drop. Arguably, it's somewhat arcane to have to make those preventDefault() calls, but as such, it's a good candidate to encapsulate in a reusable component.

Because this JavaScript unconditionally prevents the browser from canceling drag-over and drag-enter events, it unconditionally accepts all drops. That won't cut it in the real world, so in Conditional dragging, I will address that shortcoming. Next, I'll show you how to add Ajax to the drop target component.


Adding Ajax and sending a payload to the server

In Drag and drop on the client, I handled dropped links entirely on the client, by displaying an alert showing the link's title and URL (see Figure 4). For the Feeds application, and for most nontrivial JSF applications that offer drag and drop, a drop is often accompanied by a trip to the server, where the payload is incorporated into server-side data. Because of that requirement, in this section, I add Ajax to the <h5:drop> component so the component automatically makes an Ajax call when a drop occurs, sending the payload along with the Ajax call.

Every time a user drops a link on the right menu, the <h5:drop> component makes an Ajax call, which adds the link to the application's list of saved links on the server. When the Ajax call returns, JSF updates the drop target to reflect the newly added link.

Listing 7 shows the updated drop target:

Listing 7. The drop target, Take II
<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>

In this version of the drop target, I've wired the drag-and-drop payload to a bean property: #{dragDrop.payload}. And I've told the drop target to render @this — meaning the drop target itself — when the Ajax call, initiated by a drop, returns.

Listing 8 shows the updated implementation of the <h5:drop> component:

Listing 8. The <h5:drop> component, Take II
<!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>

The updated drop target needs to:

  • Make an Ajax call when a drop occurs
  • Make the payload available on the server during the Ajax call

I make the Ajax call in the <h5:drop> component's JavaScript, shown in Listing 9, with JSF's jsf.ajax.request() JavaScript function:

Listing 9. The <h5:drop> component's JavaScript, Take II
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);
      }
   };
}

Recall the payload designation in Listing 7:

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

I make the payload available through the invisible input text in the <h5:drop> component in Listing 8. To transfer the payload to the client, I need a way to shuttle a value back and forth between the client and server, and JSF already provides that with <h:inputText>. So I add an invisible input text to the drop target, and as you can see from the JavaScript in Listing 9, I set the invisible input's value when a drop is made, just before the Ajax request.

Because I set the input's value before making the Ajax call, the drag-and-drop payload, now stored in the input's value, will be available on the server during the Ajax call. Because the input text is wired to a bean property, JSF will pass the payload to that property's setter method during the Ajax call.

That value is used by the <h5:drop> component in Listing 8, as the value for the invisible text input:

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

In Listing 9, the invisible text input is specified as the source of the Ajax request:

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

Because the invisible text input is the source of the Ajax request, JSF processes the input on the server, which means it will invoke the text input's associated property-setter method — in this case, DragDrop.setPayload(), shown in Listing 10:

Listing 10. The payload property's implementation
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 passes the drag-and-drop payload to DragDrop.setPayload(). Based on that payload, the DragDrop.setPayload() method creates a new RSS item and adds it to the rssFeed's list of saved items. Note that I also include a getter method for the payload property, because JSF requires both setter and getter for input values.

It's easy to reuse existing component functionality — in this case, <h:inputText>'s ability to transfer data between client and server — with composite components. I use the invisible text input in the <h5:drop> component to transfer data from the client to the server, simply by adding the text input to the composite component and setting the input's value before making the Ajax call.

Now I've got a fairly sophisticated drop target (notice that the drag source hasn't changed since its introduction) that makes Ajax calls in response to drops and transports the transferred data from the client to the server. It also lets page authors specify the components that they want to render when the Ajax call returns. But my drag-and-drop components are still lacking one feature: conditional dragging.


Conditional dragging

In Listing 1, I implemented the list of saved links in the Feeds application as a linked list, which means users can add duplicates of the same article. I want to disallow this behavior, as shown in Figure 7. Notice that the cursor on the dragged title doesn't become a plus sign as it does for the successful drop in Figure 2.

Figure 7. Disallowing a drop (cursor does not indicate copy)
Disallowing a drop (cursor does not indicate copy)

To disallow duplicate drops, I change the <h5:drop> component's drag-enter and drag-over event handlers to accept drops conditionally, as shown in Listing 11:

Listing 11. <h5:drop> component's JavaScript, Take III
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

Notice the @Inject annotation in Listing 12. It provides access to the rssFeed managed bean. JSF applications often need access to managed beans in Java code. You can use the JSF and JSP Expression Language APIs to access those managed beans, or you can simply use the @Inject annotation.

In Adding Ajax and sending a payload to the server, I focused on transferring the drag-and-drop payload from the client to the server. To accept drops conditionally, I must do the opposite: send information from the server back to the client. In this case, I need access to the list of saved links so I can determine if a link is a duplicate. So, to transfer the requisite data from the server back to the client, I add a serverPayload variable to the dropzone.

To send the list of saved links back to the server, I store a string containing each saved link's title, in the drop target's invisible text input, by implementing DragDrop.getPayload(), as shown in Listing 12:

Listing 12. The payload property's getter method
package com.clarity;

// imports omitted. See Listing 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);
   }
}

The next time a drag is initiated, the application's drag source checks the payload from the server (which is, of course, the invisible text input's value) to see if the dragged link is a duplicate, as shown in Listing 13:

Listing 13. The drag source, Take II
<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>

If the dragged link is a duplicate, the drag source's JavaScript sets the drop target's acceptDrop attribute to false, and the drop is subsequently cancelled by the drop target.


Conclusion

In this article, I've shown you how to implement composite components with JSF 2 that encapsulate HTML5 drag and drop. Composite components are a powerful addition to the JSF developer's tool chest, because they let you implement reusable components without having to write Java code or perform any configuration. I've also illustrated how to reuse existing components, such as JSF's built-in <h:inputText> element, to reduce the amount of work you need to do when you implement your own composite components.

Once you've developed a handful of composite components, it makes sense to group them together in a JAR file so that other developers can use them. Simply JAR them up, with a META-INF directory in place of WEB-INF.

In the next JSF2 fu installment, I'll look back at what I've covered in the series and recommend some best practices for JSF 2.


Download

DescriptionNameSize
Sample code for this articlej-jsf2fu-1110.zip5.39MB

Resources

Learn

Get products and technologies

Discuss

  • Get involved in the My developerWorks community. Connect with other developerWorks users while exploring the developer-driven blogs, forums, groups, and wikis.

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Java technology on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology, Web development
ArticleID=587793
ArticleTitle=JSF 2 fu: HTML5 composite components, Part 2
publish-date=11232010