I wrote about GWT’s lack of drag and drop a long time ago, and since then have done many things instead of making a generic drag-drop mechanism as promised. Luckily, some other people have, and it seems like their solution is quite good and solves a wide array of drag-drop problems.
Since it might be interesting to some, this post will be about how a generic draggable component can be written. I’ve coded a draggable panel at the time to help make any Widget into a draggable version of itself, and I’ll describe how that was done. Warning: This is a step by step recipe on how to make a draggable widget of your own. If you’re just looking for code examples or a library, you’d better take a look at the previously mentioned project’s code and binaries.
The Basics
- Make an HTML item “floating”, so it can be located anywhere on the screen - this is called absolute location on the web page.
- Make that element draggable by having the Widget respond to mouse events and reflect the mouse movement.
- Avoid text being selected or images being dragged off the browser window, which is the default behavior of clicking and dragging on an HTML item.
The GWT implementation
First, let’s create a component that can contain any other single Widget. This component is called SimplePanel in GWT, and we’ll extend that, add the widget we want to wrap, and set the type of positioning of the panel (which is a basic div element) to be absolute.
public final class DraggableWidgetWrapper
extends SimplePanel {
public DraggableWidgetWrapper(Widget inner) {
add(inner);
DOM.setStyleAttribute(getElement(), “position”, “absolute”);
}
}
The second step is, as said, to make the panel draggable. By making it draggable, everything it contains will be dragged as well. There are four steps to make a widget draggable:
- Sink events to it using the
DOMobject.public DraggableWidgetWrapper(Widget inner) { add(inner); DOM.setStyleAttribute(getElement(), "position", "absolute"); // We don’t want to lose anything already sunk, // so just ORing it to what’s already there. DOM.sinkEvents(getElement(), DOM.getEventsSunk(getElement()) | Event.MOUSEEVENTS); } - Implement the
SourcesMouseEventsinterface, usually by using the helper classMouseListenerCollection.public final class DraggableWidgetWrapper extends SimplePanel implements SourcesMouseEvents { private MouseListenerCollection mouseListeners = new MouseListenerCollection(); public void addMouseListener(MouseListener l) { mouseListeners.add(l); } public void removeMouseListener(MouseListener l) { mouseListeners.remove(l); } // … } - Override the
onBrowserEventmethod and fire the mouse events accordingly.public class DraggableWidgetWrapper extends SimplePanel implements SourcesMouseEvents { // … public void onBrowserEvent(Event event) { switch (DOM.eventGetType(event)) { case Event.ONMOUSEDOWN: case Event.ONMOUSEUP: case Event.ONMOUSEMOVE: case Event.ONMOUSEOVER: case Event.ONMOUSEOUT: mouseListeners.fireMouseEvent(this, event); break; } } } - Implement a
MouseListener(usually usingMouseListenerAdapter) to set theleftandtopattributes of thedivelement.public class DraggableWidgetWrapper extends SimplePanel implements SourcesMouseEvents { public DraggableWidgetWrapper(Widget widget) { add(inner); DOM.setStyleAttribute(getElement(), "position", "absolute"); DOM.sinkEvents(getElement(), DOM.getEventsSunk(getElement()) | Event.MOUSEEVENTS); addMouseListener(new DraggableMouseListener()); } // … private class DraggableMouseListener extends MouseListenerAdapter { private boolean dragging = false; private int dragStartX; private int dragStartY; public void onMouseDown(Widget sender, int x, int y) { dragging = true; // capturing the mouse to the dragged widget. DOM.setCapture(getElement()); dragStartX = x; dragStartY = y; } public void onMouseUp(Widget sender, int x, int y) { dragging = false; DOM.releaseCapture(getElement()); } public void onMouseMove(Widget sender, int x, int y) { if (dragging) { // we don’t want the widget to go off-screen, so the top/left // values should always remain be positive. int newX = Math.max(0, x + getAbsoluteLeft() - dragStartX); int newY = Math.max(0, y + getAbsoluteTop() - dragStartY); DOM.setStyleAttribute(getElement(), “left”, newX); DOM.setStyleAttribute(getElement(), “top”, newY); } } } }
The third step is a tricky one, because by using the first two steps you’ll actually get a draggable widget - but it won’t be just perfect. There will be little glitches: Dragging an image will sometimes take the image as if to copy it locally (out of the browser), dragging text will select some text, either the text dragged or something behind it, etc. To make sure these won’t happen, we need to cancel the default browser behaviour for the dragged object. This is accomplished by previewing all events coming through, using the EventPreview interface.
public class DraggableWidgetWrapper
extends SimplePanel
implements SourcesMouseEvents, EventPreview {
public DraggableWidgetWrapper(Widget widget) {
add(inner);
DOM.setStyleAttribute(getElement(), “position”, “absolute”);
DOM.sinkEvents(getElement(),
DOM.getEventsSunk(getElement()) | Event.MOUSEEVENTS);
DOM.addEventPreview(this);
addMouseListener(new DraggableMouseListener());
}
// …
public boolean onEventPreview(Event event) {
if (DOM.eventGetType(event) == Event.ONMOUSEDOWN &&
DOM.isOrHasChild(getElement(), DOM.eventGetTarget(event))) {
DOM.eventPreventDefault(event);
}
// Always returning true as we don’t want to cancel
// the event, just to prevent the default behaviour.
return true;
}
}
Some Cosmetics
Personally, I don’t like the implementation details revealed from an API point of view, so I added a little factory-method to wrap things up:
public final DragDrop {
public static Widget makeDraggable(Widget widget) {
return new DraggableWidgetWrapper(widget);
}
}
And viola! Usage is as simple as DragDrop.makeDraggable(nonDraggable)! Well, since there is already a great library out there, I hope this was somewhat educational for you, if not useful for your personal project.

October 13th, 2007 at 12:54 am
[…] The Chaotic Java Blog has a really nice, well-written tutorial on how to do drag and drop with GWT. Since it might be interesting to some, this post will be about how a generic draggable component can be written. I’ve coded a draggable panel at the time to help make any Widget into a draggable version of itself, and I’ll describe how that was done. […]
November 19th, 2007 at 3:37 pm
Thanks for the post! A few questions though:
1. What is DOM.getEventTarget(event) ? I looked at the DOM API and could only find DOM.eventGetCurrentTarget(event). I presume thats what you meant?
2. I’m getting some strange results with the coordinates calulated with the onMouseMove() method. I did the following in onModuleLoad():
RootPanel.get().add(DragDrop.makeDraggable(new Button(”Drag ME”)));
Whenever I drag the Button, it ends up going to position (0,0) on the screen and stays there. Am I doing something wrong?
November 27th, 2007 at 9:46 pm
Seth,
Thanks for your feedback. Yes, you are correct: there is no “DOM.getEventTarget” - I must have re-phrased the method in my head, the one I wanted to write is “DOM.eventGetTarget”.
This method differs from the one you suggested, so try using it and see if you keep having trouble with the button, as it shouldn’t be problematic. On the other hand, since making a widget draggable cancels its natural click-behavior, I wouldn’t suggest making a button draggable, rather maybe making a panel draggable and place the button on that..
December 12th, 2007 at 4:32 am
Hi.
A really nice tutorial. I tried it, and it worked with just a little bit of tweaking.
About Seth’s coordinates Problem: I experienced the same odd behaviour, but fixed it by just changing the minus operators to plus.
“dragStartX + x + getAbsoluteLeft()” and so on.
That worked just nearly perfect. The dragable moved about 10px south-east of the cursor.
My only real problem with this tutorial is, it runs very slowly on my machine. It seems to be some kind of lag in the event handeling process.
I don’t know…
I’d be gratefull for any sugestions.
greez
December 13th, 2007 at 8:05 pm
@DaPsYcHo: Does it lag when you use gwt-dnd as well, or just with the output of this tutorial? Also, what machine are you using (Windows, Linux, Mac?)
The GWT “promise” is that everything will work on any browser and platform (or at least the popular ones) - it doesn’t specify how fast it would work, though
It sounds odd to me that you had to switch the operators but I’ll check it.
December 14th, 2007 at 5:17 am
[…] Drag and Drop in GWT - The How-To DOM.sinkEvents(getElement(), DOM.getEventsSunk(getElement()) | Event.MOUSEEVENTS); (tags: gwt howto) December 13th 2007 Posted to Links […]
January 3rd, 2008 at 4:16 pm
Great how to !
Thanks, only mistake what you have done is with counting newX and newY, there should be: getAbsoluteLeft()-dragStartX + x; and newY = getAbsoluteTop()-dragStartY +y;
Cheers from Poland
January 3rd, 2008 at 9:30 pm
Mazurek: As you can see, I’ve been commented about the mistake in the newX/Y already - I really need to sit down and see what’s wrong there and correct it.
Glad you enjoyed the how-to, and thanks for your comment!
March 31st, 2008 at 9:14 pm
Thanks for the illuminating article on the Draggable widget, it was eye opening indeed .
I however have a request to make , can you tell me how using GWT I can get a Popup to emerge in a grid when my mouse hovers over a particular column? I am sure it pretty much follows the same logic as the Draggable widget right?
March 31st, 2008 at 10:14 pm
insaneyogi: You are most likely correct. You will probably need to extend the Grid class and implement the SourceMouseEvents interface like instructed here (you need only the MouseOver and MouseOut events, but you can implement the rest as well). Then, register an event listener to the event you expose which starts and stops a dialog box when the mouse enters or exits the grid cell, respectively.
I am not a 100% sure this works, as the Grid is implemented as an html table, and it might cause problems (as the cells themselves are not the element you are adding the mouse listeners to). However, you can try it. Another issue you will need to deal with is finding out which cell the mouse is over at that moment.
Let me know how it goes!
May 2nd, 2008 at 12:59 pm
Hi!
Great how to! Congratulations! It was what I was looking for!
But I am not able to make it work properly. The widget I made ‘draggable’ is a dialog box. To open it, if I just do db.show(), in Internet Explorer it works fine, but not in Firefox (it’s like the pixels are different…). If I open it by calling db.center(), when I try to drag the dialog box, it moves ‘too much’ (the x and y coordenates are increased by the position where the dialog box was oppened).
Do you know where is the problem?
Thanks you very much!
May 2nd, 2008 at 2:39 pm
@DaPsYcHo: You are correct, there was a problem with the plus and minus signs. The equation you provided was almost correct, as the dragStartX/Y needed to be in minus. This fixes the behavior of the dragged element being a bit far from the cursor.
@Laia: The DialogBox class is already draggable by design - it might be the reason for this weird behavior. Did you try it with other classes?
May 2nd, 2008 at 2:40 pm
To everyone, in general: I’ve fixed the how-to and now it should work perfectly with no weird compilation errors or mistakes in X/Y calculations! That is why the comments above might seem odd…