Drag to Reorder Items

One of the major functions that I need for a couple of projects I’ll be working on is the ability to drag items in a list to reorder them. My wife sometimes uploads items to her site, EmilySculpts, but occasionally they’re not uploaded sequentially. It’d be nice to be able to drag the items in the gallery to re-order them.

For purposes of this demo, we’ll just be reordering objects in an ArrayCollection, but you could easily just update position indexes in a database based on the updated orders.

First, we’ll just set up a custom ItemRenderer called ImageRenderer that will display each of our images in a gallery.

<?xml version="1.0" encoding="utf-8"?>
<mx:VBox xmlns:mx="http://www.adobe.com/2006/mxml" width="120" height="140" horizontalAlign="center">
 <mx:Image source="images/{data.source}" />
 <mx:Label text="{data.name}"/>
</mx:VBox>

Next, set up some bindable variables we’ll use. galleryData is an ArrayCollection holding objects that will populate our images. output is just a string that we will use to track the values of our arrayCollection. hs is the HTTPService that we will use to load our external data. For this demo, I’ve just used a flat xml file. This could just as easily be any xml feed you generate dynamically .

[Bindable] private var galleryData:ArrayCollection ;
 [Bindable] private var output:String = '';
 private var hs:HTTPService = new HTTPService() ;

The last bit of our setup will be a simple TileList and TextArea. The TileList will use our custom ImageRenderer to render the data in galleryData.

<mx:TileList id="myTilelist"  width="650" height="200" itemRenderer="renderers.ImageRenderer" />
 <mx:TextArea text="{output}" width="200" height="400"/>

We’ll call a custom function called init() from the applicationComplete event of our application. Here, we set up our HTTPService along with a handler to handle it’s results. Finally, send the request for the data.

// use httpservice to get our external data
 hs.url = "data/gallery.xml" ;
 hs.addEventListener(ResultEvent.RESULT,hsResult) ;
 hs.send() ;

Set up the result handler function to populate galleryData. DataProvider ArrayCollections should be populated by addressing the individual items in the xml. In this case, gallery is the root node and image is the child that we want to populate the ArrayCollection with. Notice that I’ve set the dataProvider property via Actionscript. You could do this in the TileList MXML declaration just as easily. Finally, we set the value of output to a string returned by ObjectUtil.toString, which just does a  simple dump of an object’s properties and values.

private function hsResult(r:ResultEvent):void {
 // dataprovider arrays are set based on the objects you want to display
 galleryData = r.result.gallery.image;
 // set the dataprovider
 myTilelist.dataProvider = galleryData ;
 // display the array so we can make sure values are updating properly
 output = ObjectUtil.toString(galleryData.source) ;
 }

Running the application at this point would give you a basic gallery populated by xml with a TextArea displaying the values feeding galleryData. This would be just fine for the front end side of a gallery, but we want to be able to drag to reorder the images.

Enabling dragging is simple. Just set the dragEnabled property of the TileList to true. This will allow you to drag an item out of the TileList, but to allow the item to be dropped into the TileList, the dropEnabled property must also be set to true.

<mx:TileList id="myTilelist"  width="650" height="200" itemRenderer="renderers.ImageRenderer"  dragEnabled="true" dropEnabled="true" />

If you run the application now, you can drag the images to reorder them within the TileList. Watch the output display though – nothing is changing. Simply reordering the images within the TileList does nothing to our array, so we need to handle that next.

First, set the dragDrop event of the TileList to call a custom function that we’ll call reorderItems.

<mx:TileList id="myTilelist"  width="650" height="200" itemRenderer="renderers.ImageRenderer"  dragEnabled="true" dropEnabled="true" dragDrop="reorderItems(event)"/>

Finally, set up the reorderItems function. The DragEvent passed to the function has a currentIndex property, which in turn holds a selectedIndex value. This is the index position of the image that was dragged before it was dragged. So, if you click and drag the third image, de.currentIndex.selectedIndex would hold the value 2 (remember, arrays in Actionscript are zero-based). Store this value in a local variable called selectedIndex.

TileLists have a special built in method called calculateDropIndex which returns the index where an item was dropped. In other words, the new index position of our dragged and dropped image. Store this in a local variable called newPos.

In order to reorder the items in the ArrayCollection, we’ll grab a copy of the item that was dragged, insert it into the Array at the new index (newPos), and then delete the original from the original index (origPos). There is one “gotcha” though. If newPos is less than origPos, and you remove the item at origPos after the insert, you end up with duplicates of the image item. You need to remove the item from origPos + 1.

private function reorderItems(de:DragEvent):void {
 // selectedIndex holds the item that was clicked and dragged
 var origPos:int = de.currentTarget.selectedIndex ;
 // calculateDropIndex automatically figures out the position the item was dropped into
 var newPos:int = myTilelist.calculateDropIndex(de) ;
 // don't bother doing anything if it wasn't actually moved
 if (origPos != newPos) {
 // grab a copy of the item that was dragged
 var theItem:Object = galleryData[origPos] ;
 // insert the copy into the new position
 galleryData.addItemAt(theItem,newPos) ;
 if (newPos > origPos) {
 // if it's a higher index, ok to just remove the original
 galleryData.removeItemAt(origPos) ;
 } else {
 // if its a lower index, need to remove one up, otherwise you get a duplicate
 galleryData.removeItemAt(origPos + 1) ;
 }
 }
 // set the output data
 output = ObjectUtil.toString(galleryData.source) ;

 }

Now, if you run the application, you can drag & drop the images to reorder them, and the ArrayCollection values will be properly updated with the correct order. You can monitor this in the TextArea.

This technique could be used for reordering anything – not just gallery images. Reorder a schedule, or elements in a custom layout. The possibilities are endless. View the final application here.

You can leave a response, or trackback from your own site.

Leave a Reply