Click here to Skip to main content
15,879,326 members
Articles
Article
(untagged)

Google Web Toolkit Solutions: Chapter 6: Drag and Drop

Rate me:
Please Sign up or sign in to vote.
5.00/5 (1 vote)
19 May 2008CPOL20 min read 39.7K   9   1
The Google Web Toolkit is a development framework for creating Ajax-enabled web applications in Java. This chapter implements drag-and-drop capabilities for a web UI using the GWT.

Solution 6: Drag and Drop

The ultimate in user interactivity, drag and drop is taken for granted in desktop applications but is a litmus test of sorts for web applications: If you can easily implement drag and drop with your web application framework, then you know you've got something special.

Until now, drag and drop for web applications has, for the most part, been limited to specialized JavaScript frameworks such as Script.aculo.us and Rico.1 No more. With the advent of GWT, we have drag-and-drop capabilities in a Java-based web application framework. Although GWT does not explicitly support drag and drop (drag and drop is an anticipated feature in the future), it provides us with all the necessary ingredients to make our own drag-and-drop module.

In this solution, we explore drag-and-drop implementation with GWT. We implement drag and drop in a module of its own so that you can easily incorporate drag and drop into your applications.

Stuff You're Going to Learn

This solution explores the following aspects of GWT:

  • Implementing composite widgets with the Composite class
  • Removing widgets from panels
  • Changing cursors for widgets with CSS styles
  • Implementing a GWT module
  • Adding multiple listeners to a widget
  • Using the AbsolutePanel class to place widgets by pixel location
  • Capturing and releasing events for a specific widget
  • Using an event preview to inhibit browser reactions to events

See Solution 1 and Solution 2 for more in-depth discussions of implementing GWT modules and implementing composite widgets, respectively.

The Drag-and-Drop Example Application

Our discussion of drag and drop (dnd) starts with a sample application that uses our drag-and-drop module. Then we peel back the layers of the drag-and-drop onion to reveal the underlying implementation.

Figure 6.1 shows the drag-and-drop example application in action. The application contains iPods and Zunes that can be dragged into their respective shopping carts. When you start dragging a music player, the cursor changes to the pointer cursor to indicate that a drag is underway, just in case the actual movement of the music player is not enough evidence of that fact.

Image 1

Figure 6.1
The drag-and-drop example application

If a user drags a music player, which in dnd parlance is known as a drag source, over its shopping cart (referred to as a drop target), two things happen: We once again change the cursor, this time to a move cursor, to indicate that a drop is acceptable for this drop target (known as a drag-over effect), and we change the border of the drop target (known as a drag-under effect). If the user subsequently releases the mouse while the drag source is over the drop target, we remove the drag source from the page and update the drop target to reflect the fact that it now contains the music player that was dropped.

If the user starts dragging a music player and then decides against dropping it on its shopping cart panel, we scoot the music player back to its original position, as illustrated in Figure 6.2. This is standard drag-and-drop behavior.

Image 2

Figure 6.2
Drag sources snap back when dropped outside a drop target

Finally, notice that we have two drop targets: one for iPods and another for Zunes. Users cannot drag an iPod into the Zune shopping cart, or vice versa. If they try to do so, the cursor changes to the no-drop cursor when the music player enters the forbidden shopping cart, as shown in Figure 6.3. When a user drops a music player over a forbidden shopping cart, the music player moves back to its original position, just as it does when dropped outside any drop target.

Image 3

Figure 6.3
Disallowing drops in drop targets

Our drag-and-drop application uses a drag-and-drop module. We discuss that module in detail in "Drag and Drop Implementation in a GWT Module," but for now let's see what's involved in using that module.

The Drag-and-Drop Module

The drag-and-drop application and its associated files and directories are shown in Figure 6.4.

Image 4

Figure 6.4
The drag-and-drop application's files and directories

The application is made up primarily of five things: Java source files; images; a CSS file; a configuration file; and an HTML page. In Solution 1, we showed you how to use custom widgets that were packaged in a module. For the drag-and-drop application, we employ the same technique—a two-step process—to use the drag-and-drop module:

  • Inherit the module with an inherits element in the configuration file.

  • Include the module's JAR file in our application's classpath.

We showed you how to include GWT Solutions Components module in your application's classpath in "Custom Widget Use," so we don't cover that ground again, but we do show you how we inherit the drag-and-drop module in the application's configuration file.

Inheriting the Drag-and-Drop Module in an Application's Configuration File

The XML configuration file for our application is shown in Listing 6.1.

Listing 6.1 com/gwtsolutions/DragAndDrop.gwt.xml

1.<module>
2.
3.   <!— Inherit the core Web Toolkit stuff. —>
4.   <inherits name='com.google.gwt.user.User'/>   
5.   
6.   <!— Inherit the I18N stuff. —>   
7.  <inherits name="com.google.gwt.i18n.I18N"/>
8.
9.   <!— Inherit the drag and drop stuff. —>
10.   <inherits name='com.gwtsolutions.components.Components'/>
11.   <inherits name='com.gwtsolutions.components.client.ui.Dnd'/>
12.
13.   <!— Include CSS stylesheet. —>
14.   <stylesheet src="styles.css"/>
15.   
16.   <!— Specify the app entry point class. —>
17.   <entry-point class='com.gwtsolutions.client.DragAndDrop'/>
18.  
19.</module>

The drag-and-drop application uses GWT internationalization, so we inherit GWT's I18N module in addition to the User module.

The drag-and-drop module resides in the GWT Solutions Components module, so we inherit both of those modules in our application's configuration file.

The configuration file also includes its CSS stylesheet in the configuration file. We could have included the stylesheet with a standard link element in the application's HTML page, but including stylesheets in GWT configuration files is a more reusable solution because users can reuse your stylesheet along with your module. No one's ever going to reuse our application's module, but just the same, we prefer including stylesheets in configuration files to HTML pages in general.

Finally, we specify the entry point class for our application, com.gwtsolutions.client. DragAndDrop.

Now that we've seen how the drag-and-drop application uses the drag-and-drop module, let's look at the code for the application. We revisit the drag-and-drop module in "Drag and Drop Implementation in a GWT Module," where we look at the module's implementation.

Implementation of the Drag-and-Drop Application

Listing 6.2 shows the drag-and-drop application's class.

Listing 6.2 com.gwtsolutions.client.DragAndDrop

20.package com.gwtsolutions.client;
21.
22.import com.google.gwt.core.client.EntryPoint;
23.import com.google.gwt.core.client.GWT;
24.import com.google.gwt.user.client.ui.AbsolutePanel;
25.import com.google.gwt.user.client.ui.RootPanel;
26.
27.public class DragAndDrop implements EntryPoint {
28.  public void onModuleLoad() {
29.    DragAndDropConstants constants =
30.        (DragAndDropConstants) GWT
31.            .create(DragAndDropConstants.class);
32.
33.    final AbsolutePanel ap = new AbsolutePanel();
34.
35.    ap.add(new IpodDropTarget(new ShoppingCartPanel(constants
36.        .iPodsOnly())), 125, 10);
37.
38.    ap.add(new ZuneDropTarget(new ShoppingCartPanel(constants
39.        .zunesOnly())), 125, 260);
40.
41.    final MusicPlayer blackIpod =
42.        new MusicPlayer("images/ipod-nano-black.jpg",
43.            constants.blackIPodInfo());
44.
45.    final MusicPlayer blackZune =
46.        new MusicPlayer("images/zune-black.jpg", constants
47.            .blackZuneInfo());
48.
49.    final MusicPlayer silverIpod =
50.        new MusicPlayer("images/ipod-nano-silver.jpg",
51.            constants.silverIPodInfo());
52.
53.    final MusicPlayer brownZune =
54.        new MusicPlayer("images/zune-brown.jpg", constants
55.            .brownZuneInfo());
56.
57.    ap.add(new MusicPlayerDragSource(blackIpod), 10, 20);
58.    ap.add(new MusicPlayerDragSource(brownZune), 10, 120);
59.    ap.add(new MusicPlayerDragSource(silverIpod), 10, 200);
60.    ap.add(new MusicPlayerDragSource(blackZune), 10, 300);
61.
62.    ap.addStyleName("dragPanel");
63.    RootPanel.get().add(ap);
64.  }
65.}

The preceding code is straightforward. We create an absolute panel, to which we add two shopping cart panels, each wrapped in a drop target. Then we create four music players and add each of them, wrapped in music player drag sources, to the absolute panel. After that flurry of activity, we have an absolute panel with four drag sources and two drop targets. Finally, we attach a CSS style to the absolute panel and add it to the root panel of the page.

The MusicPlayer and ShoppingCartPanel classes are GWT composite widgets. Let's look at their implementations before we dive into the dnd module.

Using the Music Player and Shopping Cart Panel Components

The MusicPlayer class is listed in Listing 6.3.

Listing 6.3 com.gwtsolutions.client.MusicPlayer

1.package com.gwtsolutions.client;
2.
3.import com.google.gwt.user.client.ui.Composite;
4.import com.google.gwt.user.client.ui.Image;
5.
6.public class MusicPlayer extends Composite {
7.  private Image image;
8.  private String info;
9.
10.  public MusicPlayer(String imageUrl, String info) {
11.    image = new Image(imageUrl);
12.    this.info = info;
13.    initWidget(image);
14.  }
15.
16.  public String getInfo() {
17.    return info;
18.  }
19.}

This is about as simple as a composite widget gets. The music player composite contains an image and some information about the player. Notice the call to the Composite class's initWidget method. As with all composite widgets that extend Composite, you must call that method in the constructor.

The shopping cart panel composite is listed in Listing 6.4.

Listing 6.4 com.gwtsolutions.client.ShoppingCartPanel

1.package com.gwtsolutions.client;
2.
3.import com.google.gwt.user.client.ui.Composite;
4.import com.google.gwt.user.client.ui.HorizontalPanel;
5.import com.google.gwt.user.client.ui.Image;
6.import com.google.gwt.user.client.ui.Label;
7.import com.google.gwt.user.client.ui.VerticalPanel;
8.
9.public class ShoppingCartPanel extends Composite {
10.  private final HorizontalPanel hp = new HorizontalPanel();
11.  private final VerticalPanel vp = new VerticalPanel();
12.
13.  public ShoppingCartPanel(String title) {
14.    initWidget(hp);
15.    hp.add(new Image("images/shopping_cart.gif"));
16.    hp.addStyleName("cartPanel");
17.    vp.add(new Label(title));
18.    hp.add(vp);
19.  }
20.
21.  public void add(MusicPlayer ipod) {
22.    vp.add(new Label(ipod.getInfo()));
23.  }
24.}

This composite contains a horizontal panel that in turn contains the shopping cart image and a vertical panel. The vertical panel initially contains only a title. When a music player is dropped on a drop target, the drop target invokes ShoppingCartPanel. add() to add the music player to the cart. That add method simply adds the music player's information, in the form of a GWT label, to the vertical panel.


com.google.gwt.user.client.ui.HorizontalPanel

  • void add(Widget w)

Adds a widget to a horizontal panel. This method creates a table data (<td>) element, places the widget's DOM element in the table data, and adds the table data to the lone table row created by the horizontal panel. That table row resides in a table that's created by the vertical panel's subclass, CellPanel. The method then sets the horizontal and vertical alignments for the widget to left and top, respectively.



com.google.gwt.user.client.ui.VerticalPanel

  • void add(Widget w)

Adds a widget to a vertical panel. This method creates a table row (<tr>) and a table data (<td>) element, adds the widget's DOM element to the table data, and adds the table row to the table that's created by the vertical panel's subclass, CellPanel. The method then sets the horizontal and vertical alignments for the widget to left and top, respectively.


Using Drag Sources and Drop Targets

We've seen the application and its two composite widgets. Now things start to get interesting because next we look at how you implement your own drag sources and drop targets by using the drag-and-drop module.

Our sample application implements a single drag source—the MusicPlayerDragSource class—and two drop targets: IpodDropTarget and ZuneDropTarget. Let's start with the drag source, which is listed in Listing 6.5.

Listing 6.5 com.gwtsolutions.client.MusicPlayerDragSource

1.package com.gwtsolutions.client;
2.
3.import com.gwtsolutions.components.client.ui.dnd.DragSource;
4.import com.gwtsolutions.components.client.ui.dnd.DropTarget;
5.
6.public class MusicPlayerDragSource extends DragSource {
7.  public MusicPlayerDragSource(MusicPlayer musicPlayer) {
8.    super(musicPlayer);
9.  }
10.
11.  public void dragStarted() {
12.    addStyleName("pointerCursor");
13.  }
14.
15.  public void droppedOutsideDropTarget() {
16.    super.droppedOutsideDropTarget();
17.    removeStyleName("pointerCursor");
18.  }
19.
20.  public void acceptedByDropTarget(DropTarget dt) {
21.    removeStyleName("pointerCursor");
22.  }
23.
24.  public void rejectedByDropTarget(DropTarget dt) {
25.    super.rejectedByDropTarget(dt);
26.    removeStyleName("pointerCursor");
27.  }
28.}

This class extends the DragSource class, which is part of our dnd module. That DragSource class implements four methods that subclasses are likely to override:

  • void dragStarted()

  • void droppedOutsideDropTarget()

  • void acceptedByDropTarget(DropTarget dt)

  • void rejectedByDropTarget(DropTarget dt)

The preceding methods are called by the dnd module when one of the following occurs: The drag starts; the drag source is dropped outside a drop target; or the drop is accepted or rejected by a drop target.

When the drag starts, the music player drag source adds to itself the CSS style named pointerCursor. That style defines a single property, the cursor property, with the value pointer. Setting that style effectively changes the cursor when it's over our drag source. See Listing 6.9 for the definition of that CSS style.

When a music player drag source is dropped outside any drop target, we invoke super.droppedOutsideDropTarget(), which returns the drag source to its original position, and we reset the cursor by removing the pointerCursor style from the drag source widget.

When a music player drag source is dropped on a drop target that rejects the drop, we invoke super.droppedOutsideDropTarget(), which returns the drag source to its original position and resets the cursor. Notice that in this case, dropping a music player outside a drop target has the same effect, from the point of view of the drag source, as being rejected by a drop target.

When a music player drag source is dropped on a drop target that accepts the drop, we simply reset the cursor. It's up to the drop target to add the music player to the drop target's enclosed panel.

We only have one drag source class, because iPods and Zunes react identically when they are dragged and dropped; however, we need two drop targets because the iPod drop target only accepts iPods and the Zune drop target only accepts Zunes. That said, however, the two kinds of drop targets are much more similar than they are different, so we have a base class that encapsulates those similarities. That drop target base class is listed in Listing 6.6.

Listing 6.6 com.gwtsolutions.client.MusicPlayerDropTarget

1.package com.gwtsolutions.client;
2.
3.import com.google.gwt.user.client.ui.AbsolutePanel;
4.import com.google.gwt.user.client.ui.Widget;
5.import com.gwtsolutions.components.client.ui.dnd.DragSource;
6.import com.gwtsolutions.components.client.ui.dnd.DropTarget;
7.
8.public abstract class MusicPlayerDropTarget extends DropTarget {
9.  public MusicPlayerDropTarget(Widget w) {
10.    super(w);
11.  }
12.
13.  public void dragSourceEntered(DragSource ds) {
14.    if (acceptsDragSource(ds)) {
15.      ds.addStyleName("moveCursor");
16.      addStyleName("moveCursor");
17.      addStyleName("blueBorder");
18.    }
19.    else {
20.      ds.addStyleName("noDropCursor");
21.      addStyleName("noDropCursor");
22.    }
23.  }
24.
25.  public void dragSourceExited(DragSource ds) {
26.    if (acceptsDragSource(ds)) {
27.      ds.removeStyleName("moveCursor");
28.      removeStyleName("moveCursor");
29.      removeStyleName("blueBorder");
30.    }
31.    else {
32.      ds.removeStyleName("noDropCursor");
33.      removeStyleName("noDropCursor");
34.    }
35.  }
36.
37.  public void dragSourceDropped(DragSource ds) {
38.    super.dragSourceDropped(ds);
39.
40.    if (acceptsDragSource(ds)) {
41.      ((ShoppingCartPanel) getWidget()).add((MusicPlayer) ds
42.          .getWidget());
43.
44.      ((AbsolutePanel) ds.getParent()).remove(ds);
45.
46.      removeStyleName("moveCursor");
47.      removeStyleName("blueBorder");
48.    }
49.    else {
50.      ds.removeStyleName("noDropCursor");
51.      removeStyleName("noDropCursor");
52.    }
53.  }
54.}

This class extends the DropTarget class, which is also part of our dnd module. That class implements three methods that subclasses typically override:

  • void dragSourceEntered(DragSource ds)

  • void dragSourceDropped(DragSource ds)

  • void dragSourceExited(DragSource ds)

The preceding methods are called by the dnd module when a drag source enters, exits, or is dropped on a drop target. The drop target superclass also defines one abstract method that subclasses must implement: boolean acceptsDragSource(DragSource ds), which determines whether a drop target will accept a given drag source.

When a music player drag source enters or exits a drop target, we manipulate styles depending on whether the drag source is acceptable to the drop target to achieve drag-over and drag-under effects.

When a music player drag source is dropped on the drop target, we call super.dragSourceDropped(), which notifies the drag source of the drop by calling the drag source's acceptedByDropTarget method or rejectedByDropTarget method, depending on whether or not the drop target accepts the drop.

Now that we've encapsulated common drop target behavior in a base class, let's look at the subclasses specific to iPods and Zunes, listed in Listing 6.7 and Listing 6.8.

Listing 6.7 com.gwtsolutions.public.IpodDropTarget

1.package com.gwtsolutions.client;
2.
3.import com.google.gwt.user.client.ui.Widget;
4.import com.gwtsolutions.components.client.ui.dnd.DragSource;
5.
6.public class IpodDropTarget extends MusicPlayerDropTarget {
7.  public IpodDropTarget(Widget w) {
8.    super(w);
9.  }
10.
11.  public boolean acceptsDragSource(DragSource ds) {
12.    MusicPlayer mp =
13.        (MusicPlayer) ((MusicPlayerDragSource) ds).getWidget();
14.
15.    return mp.getInfo().startsWith("iPod");
16.  }
17.}

Listing 6.8 com.gwtsolutions.public.ZuneDropTarget

1.package com.gwtsolutions.client;
2.
3.import com.google.gwt.user.client.ui.Widget;
4.import com.gwtsolutions.components.client.ui.dnd.DragSource;
5.
6.public class ZuneDropTarget extends MusicPlayerDropTarget {
7.  public ZuneDropTarget(Widget w) {
8.    super(w);
9.  }
10.
11.  public boolean acceptsDragSource(DragSource ds) {
12.    MusicPlayer mp =
13.        (MusicPlayer) ((MusicPlayerDragSource) ds).getWidget();
14.
15.    return mp.getInfo().startsWith("Zune");
16.  }
17.}

The only thing that the drop targets specific to the music player do is define what kind of music player they will accept, by checking whether the component wrapped in the drag source is an iPod or a Zune.


com.google.gwt.user.client.ui.UIObject

  • removeStyleName(String style)

Removes a CSS style from the set of styles applied to a single GWT widget.

Because you can selectively add and remove styles to a widget, you can change the way the widget looks under certain conditions, such as changing a drop target's border to indicate that a hovering draggable is acceptable (or not) for dropping on the drop target.



com.google.gwt.user.client.ui.AbsolutePanel

  • remove(Widget w)

Removes the specified widget from the absolute panel. AbsolutePanel inherits this method from its superclass, ComplexPanel. If the widget is not a child of the panel, the method does nothing; otherwise, it removes the widget's DOM element from the panel's DOM element.


Defining the CSS Classes

Listing 6.9 shows the CSS styles used by the application's drag source and drop targets.

Listing 6.9 com/gwtsolutions/public/css/styles.css

1. <style>
2.     body,td,a,div,.p{font-family:arial,sans-serif}
3.     div,td{color:#000000}
4.     a:link,.w,.w a:link{color:#0000cc}
5.     a:visited{color:#551a8b}
6.     a:active{color:#ff0000}
7.         
8.     .dragPanel {
9.       border: thin solid darkGray;
10.       width: 400px;
11.       height: 400px;
12.       background: lightGray;
13.     }
14.         
15.     .cartPanel {
16.       padding: 10px;
17.       border: thin solid darkGray;
18.       background: white;
19.       width: 250px;
20.       height: 125px;
21.     }
22.         
23.     .pointerCursor {
24.       cursor: pointer;
25.     }
26.     .moveCursor {
27.       cursor: move;
28.     }
29.     .blueBorder {
30.       border: thin solid blue;
31.     }
32.     .noDropCursor {
33.      cursor: no-drop;
34.     }
35. </style>

Take note of the cursor styles—pointerCursor, moveCursor, noDropCursor—and the blueBorder style. Each of those styles has only one attribute, and the styles are added and removed from widgets. With GWT, it is not uncommon to define CSS styles with one attribute that are mixed in with other CSS styles for a single widget.

Drag and Drop Implementation in a GWT Module

Now that we have a good grasp of how to use the dnd module, let's look at how it's implemented.

The drag-and-drop module is implemented inside, our Components module. Figure 6.5 shows the drag and drop's pertinent files and directories.

Image 5

Figure 6.5
Drag-and-drop module's files and directories

Like all GWT modules, our drag-and-drop module has an XML configuration file. Like most modules, our drag-and-drop module also has some Java classes and interfaces.

The Module Configuration File

Every GWT module must provide a configuration file. The dnd module's configuration file is listed in Listing 6.10.

Listing 6.10 com/gwtsolutions/dnd/Dnd.gwt.xml

1.<module>
2.   <inherits name='com.google.gwt.core.Core'/>
3.</module>

It doesn't get any simpler than that. All we need for our dnd module is the core GWT classes, so that's what we inherit.

Now let's look at the Java classes in the dnd module.

The Abstract Drag Source and Drop Target Classes

The DragSource class is listed in Listing 6.11.

Listing 6.11 com.gwtsolutions.components.client.ui.dnd.DragSource

1.package com.gwtsolutions.components.client.ui.dnd;
2.
3.import com.google.gwt.user.client.ui.AbsolutePanel;
4.import com.google.gwt.user.client.ui.MouseListener;
5.import com.google.gwt.user.client.ui.Widget;
6.import com.gwtsolutions.components.client.ui.MousePanel;
7.import com.gwtsolutions.components.client.ui.Point;
8.
9.public abstract class DragSource extends MousePanel {
10.  private static final String BAD_PARENT =
11.      "Drag sources must have a parent of type AbsolutePanel";
12.  private static final MouseListener defaultDragger =
13.      new FollowsMouseDragger();
14.
15.  private boolean dragging = false;
16.  private Point originalLocation = null;
17.  private DropTarget enclosingDropTarget = null;
18.
19.  public DragSource(Widget w) {
20.    // Drag sources contain only one widget, which is 
21.    // the widget passed to this constructor
22.    add(w);
23.    
24.    // Listener order is significant. See the text
25.    // of GWT Solutions for more information
26.    addMouseListener(new DragSourceListener());
27.    addMouseListener(getMouseListener());
28.  }
29.
30.  public void onLoad() {
31.    // GWT calls this method when the drag source's
32.    // DOM element is added to the browser's DOM tree.
33.    if ( ! (getParent() instanceof AbsolutePanel))
34.      throw new IllegalStateException(BAD_PARENT);
35.  }
36.  
37.  public void dragStarted() {
38.    // subclasses can override this no-op method
39.    // as needed    
40.  }
41.
42.  public void droppedOutsideDropTarget() {
43.    // By default, when a drag source is dropped outside
44.    // of any drop target, it is returned to its original
45.    // position. Subclasses can override this method to
46.    // change or augment that behavior
47.    returnToOriginalPosition();
48.  }
49.
50.  public void acceptedByDropTarget(DropTarget dt) {
51.    // subclasses can override this no-op method
52.    // as needed    
53.  }
54.
55.  public void rejectedByDropTarget(DropTarget dt) {
56.    // By default, when a drag source is rejected by
57.    // a drop target, it is returned to its original
58.    // position. Subclasses can override this method to
59.    // change or augment that behavior    
60.    returnToOriginalPosition();
61.  }
62.
63.  public boolean isDragging() {
64.    return dragging;
65.  }
66.
67.  public void setDragging(boolean dragging) {
68.    this.dragging = dragging;
69.  }
70.
71.  public void setOriginalLocation(Point originalLocation) {
72.    this.originalLocation = originalLocation;
73.  }
74.
75.  public DropTarget getEnclosingDropTarget() {
76.    return enclosingDropTarget;
77.  }
78.
79.  public void setEnclosingDropTarget(
80.      DropTarget enclosingDropTarget) {
81.    this.enclosingDropTarget = enclosingDropTarget;
82.  }
83.
84.  protected void returnToOriginalPosition() {
85.    AbsolutePanel ap = (AbsolutePanel) getParent();
86.    ap.setWidgetPosition(this, originalLocation.x,
87.        originalLocation.y);
88.  }
89.
90.  protected MouseListener getMouseListener() {
91.    return defaultDragger;
92.  }
93.}

This simple extension of the MousePanel we discussed in "The Viewport's Use of a Focus Panel: Revisited" defines three properties and implements four methods that subclasses are likely to use: dragStarted(), droppedOutsideDropTarget(), acceptedByDropTarget(), and rejectedByDropTarget().

The properties keep track of whether the mouse panel is currently being dragged, its position before the drag began, and the enclosing drop target, if any. The methods are typically overridden by subclasses, as is the case for the MusicPlayerPanelDropTarget, listed in Listing 6.6.

You may wonder why DragSource extends MousePanel. Here's why: Not all GWT widgets support mouse listeners; in fact, most do not, and we want to be able to drag any GWT component. So we wrap widgets in a mouse panel, which does support mouse listeners. Unbeknownst to users of the dnd module, they are really dragging mouse panels, which contain a single widget. We used this same technique in The Viewport's Use of a Focus Panel: Revisited." See that section for more information about mouse panels and mouse listeners.

The DragSource class adds two mouse listeners to the widget that it wraps. The first listener, an instance of DragSourceListener, which is listed in Listing 6.15, monitors the drag and invokes the abstract methods defined by the DragSource and DropTarget classes at the appropriate times.

The second listener, by default, is an instance of FollowsMouseDragger, which is listed in Listing 6.14. That implementation of the MouseListener interface drags the drag source wherever the mouse goes. Notice that the mouse listener—an instance of FollowsMouseListener—is pluggable; DragSource subclasses can override getMouseListener() to provide a different dragger.

Oh, one more thing: The order in which we add listeners is significant because that is the order in which GWT will invoke them.2 For the drag-and-drop module to function properly, the drag source listener must be added first because the DragSourceListener's onMouseUp method turns into a no-op if the drag source is not being dragged (we don't want the drag source listener to react to mouse up events if the drag source is not being dragged). Because AbstractMouseDragger.onMouseUp() sets the drag source's dragged property to false, that method must be called after the DragSourceListener.onMouseUp(). If you reverse the order of the addition of the mouse listeners, you will see that the drag-and-drop module never reacts to mouse up events.

The DropTarget class is listed in Listing 6.12.


com.google.gwt.user.client.ui.Widget

  • void onLoad()

The GWT calls this method when a widget's DOM element is attached to the browser's DOM tree. The onLoad method is a protected method in the Widget class, so it is available for overriding by subclasses, but you cannot call it directly outside a widget subclass. The onLoad method is overridden in the drag-and-drop module discussed in this section by the DragSource class to make sure that the drag source's parent widget is an instance of AbsolutePanel.


Listing 6.12 com.gwtsolutions.components.client.ui.dnd.DropTarget

1.package com.gwtsolutions.components.client.ui.dnd;
2.
3.import com.google.gwt.user.client.ui.Widget;
4.import com.gwtsolutions.components.client.ui.MousePanel;
5.
6.public abstract class DropTarget extends MousePanel {
7.  public abstract boolean acceptsDragSource(DragSource ds);
8.
9.  public DropTarget(Widget w) {
10.    // This panel conatians only one widget, which is the
11.    // widget passed to this constructor
12.    add(w);
13.  }
14.
15.  public void dragSourceEntered(DragSource ds) {
16.    // subclasses can override this no-op method
17.    // as needed
18.  }
19.
20.  public void dragSourceExited(DragSource ds) {
21.    // subclasses can override this no-op method
22.    // as needed    
23.  }
24.
25.  public void dragSourceDropped(DragSource ds) {
26.    // If the drag source dropped on this drop target
27.    // is acceptable, notify the drag source that it's been
28.    // dropped on this drop target; otherwise, notify the
29.    // drag source that it was rejected by this drop target
30.    if (acceptsDragSource(ds))
31.      ds.acceptedByDropTarget(this);
32.    else
33.      ds.rejectedByDropTarget(this);
34.  }
35.}

This is another extension of MousePanel because we want any GWT widget to be able to function as a drop target. This class provides no-op defaults for two of the three methods that subclasses are likely to override: dragSourceEntered() and dragSourceExited().

For dragSourceDropped(), if the drag source is acceptable to the drop target—indicated by the return value of acceptsDragSource(), which is an abstract method subclasses must implement—we tell the drag source that it was accepted by the drop target; otherwise, we notify the drag source that, sadly enough, it was rejected by the drop target.

Mouse Listeners

The final pieces of the dnd puzzle are the mouse listeners, where most of the complexity lies. Listing 6.13 lists the AbstractMouseDragger class, which blithely drags widgets around on an absolute panel.

Listing 6.13 com.gwtsolutions.components.client.ui.dnd.AbstractMouseDragger

1.package com.gwtsolutions.components.client.ui.dnd;
2.
3.import com.google.gwt.user.client.DOM;
4.import com.google.gwt.user.client.ui.AbsolutePanel;
5.import com.google.gwt.user.client.ui.MouseListenerAdapter;
6.
7.public abstract class AbstractMouseDragger extends
8.    MouseListenerAdapter {
9.  private int xoffset, yoffset;
10.
11.  // Subclasses implement this method to override the
12.  // proposed left edge of the dragSource after a drag
13.  protected abstract int getNextLeft(int proposedLeft,
14.      DragSource ds);
15.
16.  // Subclasses implement this method to override the
17.  // proposed top edge of the dragSource after a drag
18.  protected abstract int getNextTop(int proposedTop,
19.      DragSource ds);
20.
21.  public void onMouseDown(DragSource ds, int x, int y) {
22.    xoffset = x;
23.    yoffset = y;
24.
25.    // Enable event capturing, so that subsequent mouse
26.    // events are all sent directly to the ds's
27.    // DOM element
28.    DOM.setCapture(ds.getElement());
29.
30.    // Tell the drag source that dragging has begun
31.    ds.setDragging(true);
32.  }
33.
34.  public void onMouseMove(DragSource ds, int x, int y) {
35.    if (ds.isDragging()) {
36.      // If the drag source is being dragged, calculate
37.      // the proposed left and top, and give subclasses
38.      // a chance to adjust those values
39.      AbsolutePanel ap = (AbsolutePanel) ds.getParent();
40.      int proposedLeft = x + ap.getWidgetLeft(ds) - xoffset;
41.      int proposedRight = y + ap.getWidgetTop(ds) - yoffset;
42.
43.      int nextLeft = getNextLeft(proposedLeft, ds);
44.      int nextRight = getNextTop(proposedRight, ds);
45.
46.      // Set the drag source's position to the next
47.      // left and next right
48.      ap.setWidgetPosition(ds, nextLeft, nextRight);
49.    }
50.  }
51.
52.  public void onMouseUp(DragSource ds, int x, int y) {
53.    // Tell the drag source that dragging is done and
54.    // release the capture of mouse events that was set
55.    // in onMouseDown()
56.    ds.setDragging(false);
57.    DOM.releaseCapture(ds.getElement());
58.  }
59.
60.  protected int checkLeftBounds(int proposedLeft,
61.      DragSource dragSource) {
62.    // Adjust the left edge of the dragSource if it's outside
63.    // the bounds of it's parent panel
64.    AbsolutePanel panel =
65.        (AbsolutePanel) dragSource.getParent();
66.    int dragSourceWidth = dragSource.getOffsetWidth();
67.    int panelWidth = panel.getOffsetWidth();
68.    int nextLeft = proposedLeft;
69.
70.    if (proposedLeft + dragSourceWidth > panelWidth)
71.      nextLeft = panelWidth - dragSourceWidth;
72.
73.    nextLeft = nextLeft < 0 ? 0 : nextLeft;
74.    return nextLeft;
75.  }
76.
77.  protected int checkTopBounds(
78.    // Adjust the top edge of the dragSource if it's outside
79.    // the bounds of it's parent panel
80.    int proposedTop, DragSource dragSource) {
81.    AbsolutePanel panel =
82.        (AbsolutePanel) dragSource.getParent();
83.    int dragSourceHeight = dragSource.getOffsetHeight();
84.    int panelHeight = panel.getOffsetHeight();
85.    int nextRight = proposedTop;
86.
87.    if (proposedTop + dragSourceHeight > panelHeight)
88.      nextRight = panelHeight - dragSourceHeight;
89.
90.    nextRight = nextRight < 0 ? 0 : nextRight;
91.    return nextRight;
92.  }
93.}

This class knows nothing about drag sources or drop targets; all it does is drag widgets. Most of the logic consists of basic math that calculates the next position of a widget and checks boundaries to make sure the widget does not escape its enclosing absolute panel.

The interesting parts of the class are the calls to DOM.setCapture() and DOM.releaseCapture(), in onMouseDown() and onMouseUp(), respectively. DOM.setCapture() captures all mouse events and makes them available only to the widget that it is passed until DOM.releaseCapture() is invoked, returning event handling to normal. That provides a significant boost to performance while a widget is being dragged, which gives us time to make sophisticated calculations, like those in the DragSourceListener class, listed in Listing 6.15.

One other interesting thing about the AbstractMouseDragger class: It's abstract because it defines two abstract methods that can be implemented by subclasses to plug in a different dragging algorithm. Those methods—getNextLeft() and getNextTop()—are passed proposed locations that follow the mouse and return final locations for the current mouse movement. Those methods can be implemented by subclasses for specialized dragging, such as dragging widgets only in the horizontal or vertical directions. One of those subclasses is the FollowsMouseDragger class, listed in Listing 6.14, which follows the mouse but restricts the widget being dragged to the bounds of its enclosing absolute panel by invoking the inherited methods checkLeftBounds() and checkTopBounds().

Listing 6.14 com.gwtsolutions.components.client.ui.dnd.FollowsMouseDragger

1.package com.gwtsolutions.components.client.ui.dnd;
2.
3.// This extension of AbstractMouseDragger drags a drag source
4.// so that it follows the mouse.
5.public class FollowsMouseDragger extends AbstractMouseDragger {
6.  protected int getNextLeft(int proposedLeft,
7.      DragSource dragSource) {
8.    // Adjust left edge if the left edge is outside the
9.    // bounds of the drag source's parent panel
10.    return checkLeftBounds(proposedLeft, dragSource);
11.  }
12.
13.  protected int getNextTop(int proposedTop,
14.      DragSource dragSource) {
15.    // Adjust left edge if the top edge is outside the
16.    // bounds of the drag source's parent panel
17.    return checkTopBounds(proposedTop, dragSource);
18.  }
19.}

The DragSourceListener class, which makes callbacks to drag sources and drop targets as a widget is dragged, is listed in Listing 6.15.

Listing 6.15 com.gwtsolutions.components.client.ui.dnd.DragSourceListener

1. package com.gwtsolutions.components.client.ui.dnd;
2.
3. import com.google.gwt.user.client.DOM;
4. import com.google.gwt.user.client.Event;
5. import com.google.gwt.user.client.EventPreview;
6. import com.google.gwt.user.client.ui.AbsolutePanel;
7. import com.google.gwt.user.client.ui.MouseListenerAdapter;
8. import com.google.gwt.user.client.ui.Widget;
9. import com.google.gwt.user.client.ui.WidgetCollection;
10.import com.gwtsolutions.components.client.ui.Point;
11.
12.import java.util.Iterator;
13.
14.public class DragSourceListener extends MouseListenerAdapter {
15.  private final Point[] dsCorners = new Point[4];
16.  private final WidgetCollection dropTargets =
17.      new WidgetCollection(null);
18.
19.  // The following event preview prevents the browser
20.  // from reacting to mouse drags as the user drags
21.  // drag sources
22.  private static EventPreview preventDefaultMouseEvents =
23.    new EventPreview() {
24.      public boolean onEventPreview(Event event) {
25.        switch (DOM.eventGetType(event)) {
26.          case Event.ONMOUSEDOWN:
27.          case Event.ONMOUSEMOVE:
28.            DOM.eventPreventDefault(event);
29.        }
30.        return true;
31.      }
32.    };
33.
34.  public void onMouseEnter(Widget sender) {
35.    // Prevent the browser from reacting to mouse
36.    // events once the cursor enters the drag source
37.    DOM.addEventPreview(preventDefaultMouseEvents);
38.  }
39.  public void onMouseLeave(Widget sender) {
40.    // Restore browser event handling when the cursor
41.    // leaves the drag source
42.    DOM.removeEventPreview(preventDefaultMouseEvents);
43.  }
44.  public void onMouseDown(Widget sender, int x, int y) {
45.    // All drag sources must have an AbsolutePanel for a
46.    // parent. This restriction is enforced in the 
47.    // drag source's onLoad method
48.    AbsolutePanel parent = (AbsolutePanel)sender.getParent();
49.    Iterator widgetIterator = parent.iterator();
50.
51.    // Iterate over the parent's widgets and put all
52.    // drop targets in the dropTargets widget collection
53.    // for future reference (see intersectsDropTarget(),
54.    // implemented below)
55.    while (widgetIterator.hasNext()) {
56.      Widget w = (Widget) widgetIterator.next();
57.      if (w instanceof DropTarget) {
58.        dropTargets.add(w);
59.      }
60.    }
61.
62.    // Set the original location of the drag source in
63.    // case the drag source is dropped outside any drop
64.    // targets or is dropped on a drop target that rejects
65.    // the drag source
66.    DragSource ds = (DragSource) sender;
67.    ds.setOriginalLocation(new Point(parent.getWidgetLeft(ds),
68.        parent.getWidgetTop(ds)));
69.    
70.    // Notify the drag source that a drag has been
71.    // initiated
72.    ds.dragStarted();
73.  }
74.
75.  public void onMouseMove(Widget sender, int x, int y) {
76.    DragSource ds = (DragSource) sender;
77.    if (!ds.isDragging()) {
78.      // Don't do anything if the drag source is
79.      // not being dragged
80.      return;
81.    }
82.
83.    Widget dsWidget = ds.getWidget();
84.    DropTarget dt = intersectsDropTarget(dsWidget);
85.
86.    // If the drag source intersects a drop target...
87.    if (dt != null) {
88.      // ...and if the drag source just entered
89.      // the drop target...
90.      if (ds.getEnclosingDropTarget() == null) {
91.        // ...set the enclosing drop target and
92.        // notify the drop target that the drag source
93.        // has entered
94.        ds.setEnclosingDropTarget(dt);
95.        dt.dragSourceEntered(ds);
96.      }
97.    }
98.    // If the drag source is not intersecting a drop
99.    // target...
100.    else {
101.      DropTarget enclosingDropTarget =
102.          ds.getEnclosingDropTarget();
103.      
104.      // ...and the drag source was inside a drop target
105.      // previously...
106.      if (enclosingDropTarget != null) {
107.        // ...set the enclosing drop target to null
108.        // and notify the drop target that the drag
109.        // source has exited
110.        ds.setEnclosingDropTarget(null);
111.        enclosingDropTarget.dragSourceExited(ds);
112.      }
113.    }
114.  }
115.
116.  public void onMouseUp(Widget sender, int x, int y) {
117.    DragSource ds = (DragSource) sender;
118.    Widget dsWidget = ds.getWidget();
119.
120.    if (!ds.isDragging()) {
121.      // If the drag source is not being dragged,
122.      // do nothing
123.      return;      
124.    }
125.    
126.    DropTarget dt = intersectsDropTarget(dsWidget);
127.    if (dt != null) {
128.      // If the drag source intersects a drop target,
129.      // notify the drop target that the drag source
130.      // was dropped
131.      dt.dragSourceDropped(ds);
132.    }
133.    else {
134.      // If the drag source doesn't intersect a drop
135.      // target, notify the drag source that it was
136.      // dropped outside of any drop target
137.      ds.droppedOutsideDropTarget();
138.    }
139.  }
140.
141.  private DropTarget intersectsDropTarget(Widget dsWidget) {
142.    // Iterate over the collection of drop targets in the
143.    // drag source's enclosing panel and see if the drag
144.    // source intersects any of those drop targets; if so,
145.    // return that drop target
146.    Iterator it = dropTargets.iterator();
147.    while (it.hasNext()) {
148.      DropTarget dt = (DropTarget) it.next();
149.      int dtLeft = dt.getAbsoluteLeft();
150.      int dtTop = dt.getAbsoluteTop();
151.      int dtWidth = dt.getOffsetWidth();
152.      int dtHeight = dt.getOffsetHeight();
153.      int dsLeft = dsWidget.getAbsoluteLeft();
154.      int dsTop = dsWidget.getAbsoluteTop();
155.      int dsWidth = dsWidget.getOffsetWidth();
156.      int dsHeight = dsWidget.getOffsetHeight();
157.      dsCorners[0] = new Point(dsLeft, dsTop);
158.      dsCorners[1] = new Point(dsLeft + dsWidth, dsTop);
159.      dsCorners[2] =
160.          new Point(dsLeft + dsWidth, dsTop + dsHeight);
161.      dsCorners[3] = new Point(dsLeft, dsTop + dsHeight);
162.
163.      for (int i = 0; i < dsCorners.length; ++i) {
164.        int x = dsCorners[i].x;
165.        int y = dsCorners[i].y;
166.        if (x > dtLeft && x < dtLeft + dtWidth && y > dtTop
167.            && y < dtTop + dtHeight) {
168.          return dt;
169.        }
170.      }
171.    }
172.    return null;
173.  }
174.}

This is where most of the heavy lifting in the dnd module occurs. On a mouse down event, onMouseDown() finds all the drop targets in the drag source's enclosing absolute panel and stores them in an instance of WidgetCollection for further reference. That method also stores the drag source's location and invokes its startDragging method.

When the drag source is dragged, onMouseMove() checks to see if the drag source intersects one of the drop targets discovered in onMouseDown(); if so, it sets the drag source's enclosingDropTarget property and informs the drop target that a drag source has entered. If the drag source does not intersect a drop target but currently has an enclosing drop target, the listener informs the drop target that the drag source has exited and sets the drag source's enclosingDropTarget property to null.

When a drag source is dropped, either inside or outside a drop target, onMouseUp() informs both the drag source and drop target of the event.

Finally, notice that in onMouseEnter(), we call GWT's DOM.addEventPreview method to add an event preview to the top of the JavaScript event stack to prevent the browser from reacting to mouse drags. If we don't do that, then when a user drags an image, the browser will drag around an outline of the image as the user drags the mouse. It will not drag the image itself. Without that event preview, our drag and drop turns into mush (you might want to try removing the event preview and see the results for yourself). Subsequently, onMouseLeave() removes the event preview so that event handling returns to normal. See "Overriding a Pop-Up Panel's Default Event Handling Behavior" for a more in-depth discussion of DOM.addEventPreview() and DOM.eventPreventDefault().


com.google.gwt.user.client.ui.Widget

  • getParent()

Returns the widget's parent widget. This method returns null for the root panel and for widgets whose DOM elements have not yet been added to the DOM tree.


One final detail of our drag-and-drop module: The DragSourceListener class uses instances of the Component module's Point class, which is listed in Listing 6.16.

Listing 6.16 com.gwtsolutions.components.client.ui.Point

1.package com.gwtsolutions.components.client.ui;
2.
3.public class Point { // immutable
4.  final public int x;
5.  final public int y;
6.
7.  public Point(int x, int y) {
8.    this.x = x;
9.    this.y = y;
10.  }
11.}

Stuff We Covered in This Solution

GWT gives us all the tools we need to achieve drag and drop, and now that you have the code discussed in this solution, you don't have to do it yourself. With that code in hand, it's easy to use drag and drop in your own applications by developing the appropriate drag sources and drop targets, which can enclose any GWT widget, and then adding them to an absolute panel.

Apart from drag and drop itself, we've also explored some interesting corners of GWT, such as creating a GWT module to encapsulate reusable code, changing a widget's cursor, capturing and releasing events for a specific widget, and using event previews to inhibit the browser's reactions to events. That knowledge will come in handy as you create your own components, whether or not they are related to drag and drop.


Footnotes

1 See http://www.script.aculo.us and http://openrico.org for more information about Script.aculo.us and Rico, respectively.

2. That's a big improvement over the Abstract Window Toolkit (AWT), which does not guarantee the order in which listeners are invoked.




This sample chapter is an excerpt from the book, Google Web Toolkit Solutions: More Cool & Useful Stuff, by David Geary, Rob Gordon, Copyright 2007, Prentice Hall, All rights reserved. Download source from GWT Solutions

David M. Geary is best known for his Graphic Java series from Prentice Hall PTR and Sun Microsystems Press, but for the past two years he has been immersed in server-side Java technology. He is a member of the expert group that's defining the standard JSP technology tag library and is also a key contributor to the Apache Struts JSP technology-based application framework.

Rob Gordon is a software consultant who has worked for Motorola, Convex Computers, and, most recently, Sun Microsystems. He has extensive experience both as a Java software developer and as a developer of educational materials on operating systems, device drivers, and programming languages.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralSource Code ?????? Pin
amarasat12-May-11 5:56
amarasat12-May-11 5:56 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.