The files are presented in a tree, and the organizer allows you to drag files and folders around within the tree.
To accomplish this, I decided to use GWT's new Drag And Drop features.
I couldn't find any real documentation on how to use it, but there is a video on the subject on youtube, here:
Google I/O 2011: GWT + HTML5: A web developers dream!
The bit on Drag&Drop starts at about 21 minutes in.
It turned out to be fairly easy!
Where to start?
With GWT, you can add Drag & Drop to almost anything.I wanted to add D&D to TreeItems. As it turns out, TreeItems don't really work well as D&D targets. The target somehow ends up being the tree itself, so that you can't really drop a file onto another treeitem.
So, instead of adding D&D to the TreeItem objects, I instead created HTML objects and set those as widgets in the tree, and then added D&D to those. Labels would work too, of course, but I wanted to add icons to represent folders and files. For this tutorial, I will just use Labels, as it is a bit cleaner.
To make a widget draggable, you must do the following:
- Set the 'draggable' property
- Add a drag start handler
In the Drag Start Handler, you must set the event data, or some browsers will not allow you to drag it.
In my case, I don't really care what the data is. Instead, I will set a class field member (called 'dragging') to remember what label is being dragged.
Finally, we will copy an image of the existing label as the drag image.
Making a Label Draggable
Label draggableLabel = new Label("drag me!"); dragLabel .getElement().setDraggable(Element.DRAGGABLE_TRUE); dragLabel.addDragStartHandler(new DragStartHandler() { @Override public void onDragStart(DragStartEvent event) { // Remember what's being dragged dragging = dragLabel; // Must set anyway for FireFox event.setData("text", "hi there"); // Copy the label as the drag image event.getDataTransfer().setDragImage(getElement(), 10, 10); } });
If you were looking closely, you might have noticed that dragLabel is not final, as it would have to be in the above code. I didn't bother, as I have wrapped everything up into a class that extends Label, as you will see below.
To make a widget droppable, you must:
- Add a DragOver handler
- Add a Drop handler
The DragOver handler doesn't have to do anything. I will use it, however, to apply a style to the label.
To counter the drag-over style, I will also add a DragLeave event.
Making a Label Droppable
Label dropLabel = new Label("Drop onto me"); dropLabel.addDomHandler(new DragOverHandler() { @Override public void onDragOver(DragOverEvent event) { dropLabel.addStyleName("dropping"); } }, DragOverEvent.getType()); dropLabel.addDomHandler(new DragLeaveHandler() { @Override public void onDragLeave(DragLeaveEvent event) { dropLabel.removeStyleName("dropping"); } }, DragLeaveEvent.getType()); dropLabel.addDomHandler(new DropHandler() { @Override public void onDrop(DropEvent event) { event.preventDefault(); // Do something with dropLabel and dragging etc... } }, DropEvent.getType());
BIG NOTE: Make sure to call event.preventDefault() in the onDrop, or else the browser might navigate away from the current page!
That's about it for making my labels draggable and droppable.
For my situation, I needed to create:
- Leaf nodes for files, that are draggable and not droppable.
- Root folders that are droppable, but not draggable
- Subfolders that are both draggable and droppable.
I wrapped all of the above up into a class, called DragDropLabel.
The DropHandler for this example will move the TreeItem of the source under the TreeItem of the drop target.
Here is the implementation of my DragDropLabel class:
class DragDropLabel extends Label { private static DragDropLabel dragging = null; final boolean droppable; public DragDropLabel(String text, boolean draggable, boolean droppable) { super(text); if (draggable) { initDrag(); } if (droppable) { initDrop(); } this.droppable = droppable; if (droppable) { addStyleName("droppable"); } else if (draggable) { addStyleName("draggable"); } } private void initDrag() { getElement().setDraggable(Element.DRAGGABLE_TRUE); addDragStartHandler(new DragStartHandler() { @Override public void onDragStart(DragStartEvent event) { // Remember what's being dragged dragging = DragDropLabel.this; // Must set for FireFox event.setData("text", "hi there"); // Copy the label image for the drag icon // 10,10 indicates the pointer offset, not the image size. event.getDataTransfer().setDragImage(getElement(), 10, 10); } }); } private void initDrop() { addDomHandler(new DragOverHandler() { @Override public void onDragOver(DragOverEvent event) { addStyleName("dropping"); } }, DragOverEvent.getType()); addDomHandler(new DragLeaveHandler() { @Override public void onDragLeave(DragLeaveEvent event) { removeStyleName("dropping"); } }, DragLeaveEvent.getType()); addDomHandler(new DropHandler() { @Override public void onDrop(DropEvent event) { event.preventDefault(); if (dragging != null) { // Target treeitem is found via 'this'; // Dragged treeitem is found via 'dragging'. TreeItem dragTarget = null; TreeItem dragSource = null; // The parent of 'this' is not the TreeItem, as that's not a Widget. // The parent is the tree containing the treeitem. Tree tree = (Tree)DragDropLabel.this.getParent(); // Visit the entire tree, searching for the drag and drop TreeItems List<TreeItem> stack = new ArrayList<TreeItem>(); stack.add(tree.getItem(0)); while(!stack.isEmpty()) { TreeItem item = stack.remove(0); for(int i=0;i<item.getChildCount();i++) { stack.add(item.getChild(i)); } Widget w = item.getWidget(); if (w != null) { if (w == dragging) { dragSource = item; if (dragTarget != null) { break; } } if (w == DragDropLabel.this) { dragTarget = item; w.removeStyleName("dropping"); if (dragSource != null) { break; } } } } if (dragSource != null && dragTarget != null) { // Make sure that target is not a child of dragSource TreeItem test = dragTarget; while(test != null) { if (test == dragSource) { return; } test = test.getParentItem(); } dragTarget.addItem(dragSource); dragTarget.setState(true); } dragging = null; } } }, DropEvent.getType()); } }
And so, with that class, now all I have to do is add some of them to a tree, like so:
Tree tree = new Tree(); RootPanel.get("main").add(tree); // root is not draggable. TreeItem root = new TreeItem(new DragDropLabel("root", false, true)); tree.addItem(root); // Add some draggable and droppable folders to 'root' root.addItem(new DragDropLabel("folder1", true, true)); root.addItem(new DragDropLabel("folder2", true, true)); etc...
Here is a screenshot of the tree in action, dragging a file onto a folder.
You can download the source code for the example here.
Select "File/Download" to save it.
Note that it does not contain the GWT libs to save space.
Please leave a comment if this has helped you out!
4 comments:
Hello,
did you check it on IE9 ?
I think code is working only on FF or Chrome that supports HTML5 DnD. I think IE9 doesn't support HTML5 Dnd (at least on DIV elements).
....
Hi Sylvain,
Sorry, no I don't have a computer with IE9 on it!
Hi Jamie,
nice blog, I've followed your instructions with my celltree and the droppable label event handling is not working.
Cheers,
Matt.
Hi Jamie,
nice blog, I've followed your instructions with my celltree and the droppable label event handling is not working.
Cheers,
Matt.
Post a Comment