<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Russ Back &#187; AdvancedDataGrid</title>
	<atom:link href="http://www.russback.com/tags/advanceddatagrid/feed" rel="self" type="application/rss+xml" />
	<link>http://www.russback.com</link>
	<description>Professional Web Development</description>
	<lastBuildDate>Mon, 02 Nov 2009 08:16:33 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.8.2</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>Disabling drag on an AdvancedDataGrid row</title>
		<link>http://www.russback.com/adobe-flex/disabling-drag-on-an-advanceddatagrid-row.html</link>
		<comments>http://www.russback.com/adobe-flex/disabling-drag-on-an-advanceddatagrid-row.html#comments</comments>
		<pubDate>Tue, 18 Aug 2009 20:30:09 +0000</pubDate>
		<dc:creator>Russ</dc:creator>
				<category><![CDATA[Adobe Flex]]></category>
		<category><![CDATA[AdvancedDataGrid]]></category>
		<category><![CDATA[Drag/Drop]]></category>
		<category><![CDATA[MVC]]></category>

		<guid isPermaLink="false">http://www.russback.com/?p=539</guid>
		<description><![CDATA[Basic drag and drop in Flex is a breeze but creating custom behaviour and data management can sometimes be tricky. This article explains how to enable or disable drag on each individual row of an AdvancedDataGrid.]]></description>
			<content:encoded><![CDATA[<p>Drag and drop using an AdvancedDataGrid is just a case of setting a few properties (dragEnabled, dropEnabled are two of these) but this makes every row of your datagrid draggable, but what if you only want to enable the copy or move of particular items in the list?</p>
<p>In this article we&#8217;ll make this happen using a shop as an example. We&#8217;ll have a list of products that in the warehouse, and a list of products that are available to buy on the shop floor. To transfer stock from the warehouse to the store we&#8217;ll drag them and drop them from one list to the other but not everything in the warehouse is saleable &#8211; some of it has been recalled for safety reasons so we don&#8217;t want to be able to drag those.</p>
<p>So to start, we&#8217;ll set up a value object class for each of our products.</p>
<h2>The Product value object</h2>
<p>Nothing fancy here, just a name and availability properties and a couple of constants we use for the availability of each Product:</p>
<pre>package com.russback.valueobjects
{
  /**
   * Value Object for a Product, containing a few basic properties
   */
  [Bindable]
  public class Product
  {

    /**
     * Used for Products currently available for sale
     */
    public static const AVAILABLE:String = "productAvailable";

    /**
     * Used for Products that have been recalled due to a fault
     */
    public static const RECALLED:String = "productRecalled";

    /**
     * Name of the Product
     * @default null
     */
    public var name:String;

    /**
     * Current availability status of this Product
     * @default AVAILABLE
     */
    public var availability:String = AVAILABLE;  

    /**
     * Constructor
     */
    public function Product()
    {
    }

  }
}</pre>
<h2>Extending the AdvancedDataGrid</h2>
<p>Next we need somewhere to display each of Products and for this example I&#8217;m using the AdvancedDataGrid. This approach could equally apply to a List but the AdvancedDataGrid is something I tend to use time and again so I&#8217;m using this one here.</p>
<p>Our extended object is called ExtendedAdvancedDataGrid (pretty unimaginative to be fair) and it begins with the usual imports and a constructor that simply invokes the constructor of the super class:</p>
<pre>package com.russback.extensions
{
  import com.russback.valueobjects.Product;

  import mx.controls.AdvancedDataGrid;
  import mx.core.DragSource;
  import mx.core.IUIComponent;
  import mx.events.DragEvent;
  import mx.managers.DragManager;

  /**
   * Extends the AdvancedDataGrid to allow for dragging of grid rows depending on data properties
   */
  public class ExtendedAdvancedDataGrid extends AdvancedDataGrid
  {

    /**
     * Constructor adds creation complete event listener
     */
    public function ExtendedAdvancedDataGrid()
    {
      super();
    }</pre>
<p>Next we override the default dragStart() method of the super class. First we create a new Array object and then for each item the user has selected and dragged, we check if it&#8217;s availability property is AVAILABLE and if so, add it to our array.</p>
<p>Then if we have any data in our Array, we create a new DragSource object containing our array data and create a drag proxy that will indicate to the user which items are being dragged (we&#8217;ll look at this shortly). Finally we call the DragManager&#8217;s doDrag() method, passing it this as the drag initiator, our DragSource, event and drag proxy.</p>
<p>This approach allows you to select any row in the list but only those Products that are available will actually be added to the DragManager to move or copy.</p>
<pre>    /**
     * Only adds product to the drag if they are available
     * Uses an instance of the CustomDragProxy class as it's drag proxy image
     */
    override protected function dragStartHandler(event:DragEvent):void
    {
      var dragData:Array = new Array();

      for each (var product:Product in this.selectedItems)
      {
        if (product.availability == Product.AVAILABLE)
        {
          dragData.push(product);
        }
      }

      if (dragData.length)
      {

        var dragSource:DragSource = new DragSource();
            dragSource.addData( dragData, "items" );

        var customDragProxy:ExtendedAdvancedDataGridDragProxy = new ExtendedAdvancedDataGridDragProxy();
            customDragProxy.owner = this;

        DragManager.doDrag(this as IUIComponent, dragSource, event, customDragProxy);

      }
    }</pre>
<p>Our final two methods in this class override the dragDrop() and dragComplete() methods. The AdvancedDataGrid moves data from source and target objects out of the box but we typically don&#8217;t want to do this in a large application where a framework such as Cairngorm is in place, so all I&#8217;m doing here is effectively preventing that behaviour. In a real world application I&#8217;d fire events in this methods and then handle the data transfer in the controller but for this example we just have a couple of comments.</p>
<pre>    /**
     * Custom drop behaviour. Prevents superclass' default behaviour
     */
    override protected function dragDropHandler(event:DragEvent):void
    {
      // custom code here to respond to a drop - firing an event to handle in an
      // MVC framework could be one example

      hideDropFeedback(event);
    }

    /**
     * Custom drag complete behaviour. Prevents superclass' default behaviour
     */
    override protected function dragCompleteHandler(event:DragEvent):void
    {
      // custom code here to respond to a completed drag - firing an event to handle in an
      // MVC framework could be one example
    }

  }
}</pre>
<h2>Creating a custom drag proxy</h2>
<p>When you create your own dragStart() method and don&#8217;t define a drag proxy, Flex will visualise the drag by creating a box that matches the size of the drag source (ie the AdvancedDataGrid). This works fine but it doesn&#8217;t indicate which items are being dragged and this is important for us as although we can select all the items in our list, we can only drag the Products that are available and therefore we only want to see these in the drag proxy while we&#8217;re dragging.</p>
<p>So for this we have another class that extends the AdvancedDataGridDragProxy class. This starts with a few constants that will be used to configure the proxy. I&#8217;ve set these up as constants just to separate these display properties from the core logic so you could just as easily replace these with public variables that you set when you create a new instance of this class.</p>
<p>The proxy will be a VBox object containing an HBox for each Product in the dragged data. The constants we have are for the width and height of each row and also the X and Y offset which we&#8217;ll use to position the proxy in relation to the mouse cursor during the drag. The last part of this initial section of code is a private variable that we&#8217;ll use to hold the VBox and the constructor.</p>
<pre>package com.russback.extensions
{
  import com.russback.valueobjects.Product;

  import mx.containers.HBox;
  import mx.containers.VBox;
  import mx.controls.AdvancedDataGrid;
  import mx.controls.Image;
  import mx.controls.Text;
  import mx.controls.advancedDataGridClasses.AdvancedDataGridDragProxy;
  import mx.core.ScrollPolicy;

  /**
   * Extends AdvancedDataGridDragProxy to display a proxy of the items that
   * the ExtendedAdvancedDataGrid has allowed to be dragged
   */
  public class ExtendedAdvancedDataGridDragProxy extends AdvancedDataGridDragProxy
  {

    /**
     * Width of each item in the proxy
     * @default 200
     */
    private static const ITEM_WIDTH:int = 200;

    /**
     * Height of each item in the proxy
     * @default 25
     */
    private static const ITEM_HEIGHT:int = 25;

    /**
     * Number of pixels to offset the proxy (against the mouse position) in the horizontal direction
     * @default -10
     */
    private static const MOUSE_X_OFFSET:int = -10;

    /**
     * Number of pixels to offset the proxy (against the mouse position) in the vertical direction
     * @default -10
     */
    private static const MOUSE_Y_OFFSET:int = -10;

    /**
     * VBox used as a wrapper for the items in the proxy
     * @default new VBox()
     */
    private var _vbox:VBox = new VBox();

    /**
     * Constructor
     */
    public function ExtendedAdvancedDataGridDragProxy()
    {
      super();
    }</pre>
<p>Next we ovveride the createChildren() method of the super class to prevent the creation of that box object that the AdvanvedDataGridProxy class creates. First we create a reference to this proxy&#8217;s owner which will be an instance of our ExtendedAdvancedDataGrid class, and then we create a sorted array containing each selected item the user has selected.</p>
<p>Now we have this we can loop through each item and provided the Product is available, we call a method called createRowProxy which we&#8217;ll look at next. This method returns an HBox which we add to our VBox.</p>
<p>Finally we set the width and height of the VBox according to the number of rows we&#8217;ve created, add the VBox to the proxy and then position the proxy in relation to the mouse cursor. -10 for both X and Y constant values works nicely in this example but this is down to your design.</p>
<pre>    /**
     * Grabs the selected items from the ExtendedAdvancedDataGrid, calls the createRowProxy()
     * method for each item that can be dragged and sets the proxy's position in line with
     * the current mouse position
     */
    override protected function createChildren():void
    {

      var grid:AdvancedDataGrid = this.owner as ExtendedAdvancedDataGrid;

      var products:Array = grid.selectedIndices;
          products.sort();

      for (var i:int = 0; i < products.length; i++)
      {
        var product:Product = grid.dataProvider[products[i]] as Product;

        if (product.availability == Product.AVAILABLE)
        {
          _vbox.addChild(createRowProxy(product));
        }
      }

      _vbox.width = ITEM_WIDTH;
      _vbox.height = _vbox.numChildren * ITEM_HEIGHT;
      _vbox.horizontalScrollPolicy = ScrollPolicy.OFF;
      _vbox.verticalScrollPolicy = ScrollPolicy.OFF;

      this.addChild(_vbox);

      this.x = grid.contentMouseX + MOUSE_X_OFFSET;
      this.y = grid.contentMouseY + MOUSE_Y_OFFSET;

    }</pre>
<p>The final method in here is the createRowProxy() method which I mentioned above. Given a Product object, this creates an HBox containing an icon image and the name of the Product and returns the HBox. It's these HBoxes you will see when you drag data from our list.</p>
<pre>    /**
     * Creates an HBox with an Image and text to act as a proxy for each row of
     * dragged data in the proxy object
     * @return An HBox
     */
    private function createRowProxy(product:Product):HBox
    {
      var hBox:HBox = new HBox();
          hBox.horizontalScrollPolicy = ScrollPolicy.OFF;
          hBox.verticalScrollPolicy = ScrollPolicy.OFF;

      var image:Image = new Image();
          image.source = "assets/images/package.png";

      var name:Text = new Text();
          name.text = product.name;

      hBox.addChild(image);
      hBox.addChild(name);

      return hBox;
    }

  }
}</pre>
<h2>An item renderer just for show</h2>
<p>Before we go ahead and use our new classes, I've created an item renderer just to make the example clear. This is a straight-forward MXML component that extends HBox and shows an icon image and the name of the Product (very similar to our drag proxy we created, for obvious reasons). The only thing to note in here is that we use a move  icon for the mouse cursor if you mouseover this object and the Product is draggable. This will indicate to the user that this item can be dragged.</p>
<pre>&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;mx:HBox
  xmlns:mx=&quot;http://www.adobe.com/2006/mxml&quot;
  xmlns:valueobjects=&quot;com.russback.valueobjects.*&quot;
  creationComplete=&quot;creationCompleteHandler(event)&quot;
  &gt;

  &lt;mx:Script&gt;
    &lt;![CDATA[
      import com.russback.valueobjects.Product;
      import mx.events.FlexEvent;

      /**
       * ID of the current cursor
       * @default 0
       */
      private var _cursorID:int = 0;

      /**
       * Image to display as cursor to indicate item is draggable
       */
          [Embed(source=&quot;assets/images/arrow-move.png&quot;)]
          private var _cursorClass:Class;

      /**
       * Adds event listeners for MOUSE_OVER and MOUSE_OUT MouseEvents
       */
      private function creationCompleteHandler(event:FlexEvent):void
      {
        this.addEventListener(MouseEvent.MOUSE_OVER, mouseOverHandler);
        this.addEventListener(MouseEvent.MOUSE_OUT, mouseOutHandler);
      }

      /**
       * Sets the cursor to indicate item is draggable
       */
      private function mouseOverHandler(event:MouseEvent):void
      {
        if (data.availability == Product.AVAILABLE)
        {
          _cursorID = cursorManager.setCursor(_cursorClass);
        }
      }

      /**
       * Resets the cursor
       */
      private function mouseOutHandler(event:MouseEvent):void
      {
        cursorManager.removeCursor(_cursorID);
      }

    ]]&gt;
  &lt;/mx:Script&gt;

  &lt;valueobjects:Product id=&quot;data&quot; /&gt;

  &lt;mx:Image
    source=&quot;assets/images/package.png&quot;
    toolTip=&quot;Product available&quot;
    visible=&quot;{data.availability == Product.AVAILABLE}&quot;
    includeInLayout=&quot;{data.availability == Product.AVAILABLE}&quot;
    /&gt;

  &lt;mx:Image
    source=&quot;assets/images/bug.png&quot;
    toolTip=&quot;Product recalled&quot;
    visible=&quot;{data.availability == Product.RECALLED}&quot;
    includeInLayout=&quot;{data.availability == Product.RECALLED}&quot;
    /&gt;

  &lt;mx:Text text=&quot;{data.name}&quot; /&gt;

&lt;/mx:HBox&gt;</pre>
<h2>Putting it all together</h2>
<p>We now have all of our building blocks for the example so we dive into the MXML application. This a very simple application that  has an ArrayCollection containing a set of Product objects, an instance of our ExtendedAdvancedDataGrid class, and then an out-of-the-box AdvancedDataGrid. I've included the latter just to demonstrate that you don't need a custom class as your target.</p>
<p>The first grid (our extended class) lists everything in our warehouse and uses our ProductRenderer component as the itemRenderer for it's only column. The second grid lists everything available in the shop (nothing initially) and has drop enabled. </p>
<pre>&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;mx:Application
  xmlns:mx=&quot;http://www.adobe.com/2006/mxml&quot;
  xmlns:valueobjects=&quot;com.russback.valueobjects.*&quot;
  xmlns:extensions=&quot;com.russback.extensions.*&quot;
  layout=&quot;horizontal&quot;
  width=&quot;553&quot;
  height=&quot;300&quot;
  viewSourceURL=&quot;srcview/index.html&quot;
  &gt;

  &lt;mx:Script&gt;
    &lt;![CDATA[
      import com.russback.valueobjects.Product;
    ]]&gt;
  &lt;/mx:Script&gt;

  &lt;mx:ArrayCollection id=&quot;products&quot;&gt;
    &lt;valueobjects:Product name=&quot;Widgets&quot; /&gt;
    &lt;valueobjects:Product name=&quot;Gadgets&quot; /&gt;
    &lt;valueobjects:Product name=&quot;Gizmos&quot; availability=&quot;{Product.RECALLED}&quot; /&gt;
    &lt;valueobjects:Product name=&quot;Buttons&quot; /&gt;
    &lt;valueobjects:Product name=&quot;Wizards&quot; availability=&quot;{Product.RECALLED}&quot; /&gt;
  &lt;/mx:ArrayCollection&gt;

  &lt;extensions:ExtendedAdvancedDataGrid
    width=&quot;100%&quot;
    height=&quot;100%&quot;
    dataProvider=&quot;{products}&quot;
    dragEnabled=&quot;true&quot;
    allowMultipleSelection=&quot;true&quot;
    dropEnabled=&quot;true&quot;
    &gt;
    &lt;extensions:columns&gt;
      &lt;mx:AdvancedDataGridColumn headerText=&quot;Products in Warehouse&quot; itemRenderer=&quot;com.russback.itemrenderers.ProductRenderer&quot; /&gt;
    &lt;/extensions:columns&gt;
  &lt;/extensions:ExtendedAdvancedDataGrid&gt;

  &lt;mx:AdvancedDataGrid
    width=&quot;100%&quot;
    height=&quot;100%&quot;
    dropEnabled=&quot;true&quot;
    &gt;
    &lt;mx:columns&gt;
      &lt;mx:AdvancedDataGridColumn headerText=&quot;Products on Shop Floor&quot; itemRenderer=&quot;com.russback.itemrenderers.ProductRenderer&quot; /&gt;
    &lt;/mx:columns&gt;
  &lt;/mx:AdvancedDataGrid&gt;

&lt;/mx:Application&gt;</pre>
<p><script type="text/javascript">
    var vars = {};    
    var params = {
      base: "."
      };
    swfobject.embedSWF("http://www.russback.com/wp-content/examples/disabling-drag-on-an-advanceddatagrid-row/main.swf", "example", "553", "300", "10.0.0", "expressInstall.swf", vars, params);
      </script></p>
<h2>Example in action</h2>
<p>In the example below you can select multiple items in the warehouse list and drop them in the shop list and they'll be added to the shop list. The drag proxy for the drag should only show those items that are being dragged, which is not necessarily every item you selected before you started the drag.</p>
<p>You can <a href="/wp-content/examples/disabling-drag-on-an-advanceddatagrid-row/srcview/disabling-drag-on-an-advanceddatagrid-row.zip">download and edit the source code</a>, or right-click on the demo below and choose View Source to see how it all fits together.</p>
<div class="flash_content" id="example">
<p>You need Flash Player 10 to view this content. You can <a href="http://get.adobe.com/flashplayer/" title="Visit adobe.com">download this for free</a> from the Adobe website. If you&#8217;re using a feed reader you could <a href="http://www.russback.com/adobe-flex/disabling-drag-on-an-advanceddatagrid-row.html">try loading this post in your web browser</a>.</p>
</div>
<p>In this example we've prevented the dragDrop() and dragComplete() methods so the data doesn't get removed from the warehouse. We'd need to do some more work in our ExtendedAdvancedDataGrid class to do this as already discussed but I've not included that here in order to keep things simple. It's also possible to drag duplicate Products into the shop list, which is the default behaviour for the AdvancedDataGrid so again, we'd need to do some more work in this area to prevent dupes being added but hopefully this article provides enough information to start you on your road to customising your own drag and drop interfaces.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.russback.com/adobe-flex/disabling-drag-on-an-advanceddatagrid-row.html/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
	</channel>
</rss>
