- Stuff You're Going to Learn
- The Drag-and-Drop Example Application
- The Drag-and-Drop Module
- Implementation of the Drag-and-Drop Application
- Drag and Drop Implementation in a GWT Module
- Stuff We Covered in This Solution
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.
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 on page 181 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.
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.