Tree Drag and Drop Part 1

This article is about dropping leaves – or rather dragging and dropping leaves.

Because this is a somewhat complex topic and blog entries shouldn’t be pages long, I’m breaking this topic into several entries. I’ll cover dragging and dropping within a Tree control, from a Tree control to something else, and from something else onto a Tree control.

If you are using a Tree control then you’ve already decided what type of data to use: XMLListCollection vs. ArrayCollection (see Tree Control DataProviders). Whichever you use the Tree uses hierarchical structures. This is ideal for XML; you’ll need ArrayCollections with ArrayCollections as children to provide the structure.

I mention this because if you have not used a Tree control before, get familiar with how to provide data to the Tree. Since I believe most applications use XML as the data for the Tree, these drag and drop examples will use XML.

Drag and Drop Within the Tree

It’s pretty easy to enable a Tree control to allow the user to drag nodes around to re-arrange the tree. Think of a file browser where you can drag a file from one folder to another.

Set up your Tree control like this:

<mx:Tree x="162" y="122" width="279" height="278"
      
dataProvider="{treeDataList}"
      
labelField="@label"
      
dragEnabled="true"
      
dragMoveEnabled="true"
      
dropEnabled="true"
       />

By setting dragMoveEnabled and dropEnabled to true, the Tree’s nodes can be moved around.

DragSource

Before discussing drag and drop, it is important to understand the DragSource class. An instance of this class is provided in the event (mx.events.DragEvent) argument of the drag event handlers. The DragSource contains, among other things, the control which initiated the drag operation, the formats of the data, and access to the data keyed by the format.

For example, if you drag an item from a catalog, the DragSource data could be the image of the item and the data about the item. So the DragSource would have two formats: images and items.

In these examples you will see "items" used as the name of the format to access the data in the DragSource. Flex controls such as Tree, DataGrid, and List, use "items" as the name of the format when they automatically create a DragSource.

Dragging onto the Tree

Use dropEnabled="true" to allow the Tree control to receive drag events and listen for the following events:

<mx:Tree x="162" y="122" width="279" height="278"
       dataProvider="{treeDataList}"
       labelField="@label"
       dropEnabled="true"
       dragEnter="onDragEnter(event)"
       dragOver="onDragOver(event)"
       dragDrop="onDragDrop(event)"

       />

The dragEnter event is dispatched when the mouse drags the drag-proxy onto the Tree. The Tree must determine if it will accept the drop or not. Just because dragEnabled has been set doesn’t mean every drop has to be honored. The dragEnter event can use information in the event – such as the control that initiated the drag (called the dragInitiator) or the data being dropped.

Here is a simple dragEnter event handler:

        private function onDragEnter( event:DragEvent ) : void
        {
              DragManager.acceptDragDrop(UIComponent(event.currentTarget));
         }

The dragOver event is dispatched while the mouse is dragging the drag-proxy within the Tree’s boundaries. You could ignore this event, but you can also use it as an opportunity to provide feedback to the user. For example, the node the mouse is over could be highlighted. You could even look at the data being dragged and at the node under the mouse and decide if the drop should be allowed. For example, if the Tree represents a car dealership and you are dragging a sports car proxy over the tree, you can change the drag cursor if the sports car proxy goes into the mini-van category telling the user the drop would be unacceptable there.

Here is a dragOver event handler which determines the node the mouse is over and

      private function onDragOver( event:DragEvent ) : void
      {

          // r is the visible index in the tree
           var dropTarget:Tree = Tree(event.currentTarget);
           var r:int = dropTarget.calculateDropIndex(event);
           tree.selectedIndex = r;

           // retrieving the newly selected node, you can examine it and decide to tell
           // the user the drop is invalid by changing the feedback.

           var node:XML = tree.selectedItem as XML;
           if( node.@type == "minivan" ) {
               DragManager.showFeedback(DragManager.NONE);
               return;
           }

           // the type of drop – copy, link, or move can be reflected in the feedback as well.
           // Here the control and shift keys determine that action.

           if (event.ctrlKey)
               DragManager.showFeedback(DragManager.COPY);
           else if (event.shiftKey)
               DragManager.showFeedback(DragManager.LINK);
           else {
              DragManager.showFeedback(DragManager.MOVE);
           }
      }

The dragDrop event is dispatched when the mouse is released. The Tree can still ignore the drop here (may be the mouse was released over the mini-van category by mistake), but it is this event that copies the data being dropped into the Tree. All, none or part of the data can be used. You can create nodes or replace nodes or even make new branches – whatever is called for by the application design.

private function onDragDrop( event:DragEvent ) : void
{
      
var ds:DragSource = event.dragSource;
      
var dropTarget:Tree = Tree(event.currentTarget);

       // retrieve the data associated with the "items" format. This will be the data that
       // the dragInitiator has copied into the DragSource.

      
var items:Array = ds.dataForFormat("items") as Array;

       // determine where in the tree the drop occurs and select that node by the index; followed by
       // retrieving the node itself.

       var r:int = tree.calculateDropIndex(event);
       tree.selectedIndex = r;
       var node:XML = tree.selectedItem as XML;
       var p:*;

      
// if the selected node has children (it is type==city),
       // then add the items at the beginning

       if( tree.dataDescriptor.hasChildren(node) ) {
             p = node;
             r = 0;
       } else {
             p = node.parent();
       }

       // taking all of the items in the DragSouce, insert them into the
       //
tree using parent p.

      
for(var i:Number=0; i < items.length; i++) {
            
var insert:XML = <node />;
             insert.@label = items[i];
            
insert.@type = "restaurant";
             tree.dataDescriptor.addChildAt(p, insert, r+i);
       }
}

The dragComplete event is registered on the drag initiator (a List, for example) which will be invoked once the drag operation has finished – either by the drop being accepted and processed, the drop being rejected outright, or by the user dropping the object over a non-drop zone.

In this example, the only thing that’s done is to clear the tree of the selection made in the dragOver and dragDrop operations.

private function onDragComplete( event:DragEvent ) : void
{
    
tree.selectedIndex = -1;
}

Summary

Dragging information onto a Tree control involves deciding if the drop should be accepted. This can be done at the outset of the operation in the dragEnter event or dynamically as the drag proxy is moved around the Tree control in the dragOver event.

Next time: dragging data from the Tree control.