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.
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, 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
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
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
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
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
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
Each link in the center of the application, highlighted in Figure 5, is a drag source:
Figure 5. 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 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, as shown in Figure 6, is in the application's right menu:
Figure 6. 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.
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.
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)
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);
}
};
} |
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 methodpackage 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.
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.
| Description | Name | Size | Download method |
|---|---|---|---|
| Sample code for this article | j-jsf2fu-1110.zip | 5.39MB | HTTP |
Information about download methods
Learn
- The JSF website: Find more resources about developing with JSF.
- Exploring HTML5 with JavaServer Faces 2.0: Check out this slide presentation by Roger Kitain, the JSF co-spec lead.
- The HTML5 Specification: The official specification for the HTML5 standard — still in progress as of November 2010 — is here. Explore the Drag and drop section.
-
"Native HTML5 Drag and Drop" (Eric Bidelman, HTML5Rocks, September 2010): Learn more about HTML5 drag and drop.
- HTML5 tag reference: W3schools documents HTML5's elements.
- HTML5 Tutorial: This site publishes numerous HTML5 tutorials, including this one on drag and drop.
- JavaScript DOM: Javadoc-like documentation of the JavaScript DOM.
-
"XHTML5 in a nutshell" (Sergey Mavrody, The WHATWG Blog, July 2010): Read about polyglot HTML5.
-
developerWorks Java technology zone: Find hundreds of articles about every aspect of Java programming.
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.

Author, 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.




