Flex: Drag Sortable VBox

Among my many projects, I am working on internal AIR app that includes configuring an XML document that is consumed by a separate, public-facing, flex application. One of the features of the interface is that items are dragged onto a VBox and the children are saved in the order in which they were added; a consequence of iterating over the getChildren(). When the Flex app consumes the saved objects it displays them in order. While this is nice as it is predictable, I was obliged to enable the ordering of these items. The intuitive solution, at least for my group, was determined to be a simple draggable interface. As items are added to the VBox, the expectation is that the order can be set by simply dragging the target item and dropping it in the desired location.

A Vbox is not a List and there is not a builtin support mechanism for this. And, to prevent me from simply changing my VBox to a List, List does not have a getChildren():Array method. Not saying that there is no way to get an array of objects contained within a List, but it became obvious that a simple switch would cause me potentially more grief than just adding the functionality to a VBox. That said, I couldn't find any resources that detailed what I needed to accomplish (I've been admonished in the past for re-inventing wheels, so I try to take a look at what already has been shared to save time and effort).

So, anyway, on to discussing how to make a drag sortable VBox with Flex. I've tried to make my solution as generic as possible to make it a functional drop-in to any situation by using UIComponents, however, some situations might be more easily solved by more precisely identifying the data type of your draggable objects. The drag and drop parts aren't so bad. We need to make sure that the target VBox has dragEnter and dragDrop listeners and that its children has a mouseMove listener that starts the whole thing off. The only trick here is that we need to remember the original index of the dragged item and then calculate, at drop, what the new index ought to be.

Our setup will be something of the sort:

<mx:VBox top="20" left="20" width="300" height="250"        
dragEnter="doDragEnter(event)"
dragDrop="doDragDrop(event)"
backgroundColor="#9f9f9f"
borderStyle="solid">

    
<local:dragItem mouseMove="doMouseMove(event)"><mx:Label text="A" /></local:dragItem>
<local:dragItem mouseMove="doMouseMove(event)"><mx:Label text="B" /></local:dragItem>
<local:dragItem mouseMove="doMouseMove(event)"><mx:Label text="C" /></local:dragItem>
<local:dragItem mouseMove="doMouseMove(event)"><mx:Label text="D" /></local:dragItem>    

</mx:VBox>

Where dragItem is just a simple component based on a Canvas:

<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml"
styleName="testItem"
height="35" width="100%">


<mx:Style>    
.testItem {
background-color:#aaccff;
}    
</mx:Style>
    
</mx:Canvas>

This is mostly because I wanted a colored box to drag and didn't feel like cluttering the main code.

Once a dragItem is moved, doMouseMove is fired and we register its index and feed the DragManager. The current index, which I'll call the dragIndex (because I have to call it something), can be found by the getChildIndex() method of the VBox. Other than that, we need identify the DragInitiator, we'll need it later on to identify this object, and the DragSource as it will hold the :

private function doMouseMove(e:MouseEvent):void {
var di:UIComponent = e.currentTarget as UIComponent;

var ds:DragSource = new DragSource();
var dragIndex:int = ((e.currentTarget as UIComponent).parent as VBox).getChildIndex(e.currentTarget as UIComponent);
ds.addData(dragIndex,'index');

DragManager.doDrag(di, ds, e);            
}

The dragEnter for the VBox just needs to accept the DragDrop:

private function doDragEnter(e:DragEvent):void {            
DragManager.acceptDragDrop( UIComponent(e.currentTarget) );
}

The dragDrop is a single call to replace the dragged object at the new index:

private function doDragDrop(e:DragEvent):void {            
(e.currentTarget as VBox).addChildAt( e.dragInitiator as UIComponent , getNewIndex(e.localY,(e.currentTarget as VBox) ) );            
}    

Of course, the trick is in the getNewIndex() function. To work this out, we just need to iterate over the objects in the VBox, looking at their y position and height. If the Y coordinate of the event is on top of the object, we break the loop. At the end of the loop, we return the loop count as the new index:

private function getNewIndex(y:int,vb:VBox):int {

for (var i:int=0;i<vb.getChildren().length;i++) {
var currChild:UIComponent = vb.getChildAt(i) as UIComponent;
if ( y>
currChild.y && y<(currChild.y+currChild.height) ) {            
break;
}
}            
return (i>
=0) ? i:0;            
}

And that's it. So here is the completed drag sortable Flex VBox with source.

Comments (Comment Moderation is enabled. Your comment will not appear until approved.)
Charlie Crystle's Gravatar Thank you! First thing I found on this and it solves a problem TODAY!
# Posted By Charlie Crystle | 9/1/10 12:57 PM
Alex Cook's Gravatar Hey, nice job! Exactly what I'm looking for.

The only thing that's missing is the ability for the user to see where they're dropping to - could be as simple as a line. Any thoughts on that?
# Posted By Alex Cook | 10/30/10 1:28 PM
Hitcliff J.'s Gravatar I'd like to implement a custom drag on an AdvancedDataGrid tree structure, which only allows the drag on the branches (not leaves).
I'm having much difficultly with this, trying to use the MouseDown event about which I read quite an interesting article found by http://www.picktorrent.com/torrents/52/advanced-da... ,but not having luck! Okay, I think I've got the mousedown able to figure out if the item is a branch of leaf. Any help on how to perform the custom drag with an advanceddatagrid??
# Posted By Hitcliff J. | 6/22/11 10:04 AM
BlogCFC was created by Raymond Camden. This blog is running version 5.9.1.001. Contact Blog Owner