Drag-and-Drop

Drag-and-drop is one of the many features that set Flex applications apart from other types of applications. As you’ll see in the next few sections, it is extremely simple to enable drag-and-drop functionality for some standard components, and with a little additional work you can enable drag-and-drop functionality to any type of component.

Using Built-In Drag-and-Drop Features

The simplest way to implement drag-and-drop functionality is to use the built-in features of many of the components, including List, Tree, DataGrid, Menu, HorizontalList, PrintDataGrid, and TileList. Each of these components enables drag-and-drop in the same way. They each have a dragEnabled property and a dropEnabled property. The two properties are false by default. When you set the dragEnabled property to true for a component, the user can click and drag items. Of course, in most cases enabling a component so that a user can click and drag an item is not very useful until the user can also drop the item somewhere in the application. Typically, this is accomplished by setting the dropEnabled property of another component to true. When the dropEnabled property is set to true for a component, the user can drop an item on the component that he dragged from another component. This causes the data from that item to be added to the drop target component. Example 10-7 illustrates both a dragEnabled and a dropEnabled component working in conjunction. The first data grid contains data about the user’s music collection. The user can drag items from the music collection to the second data grid to create a playlist.

Example 10-7. A simple drag-and-drop application

<?xml version="1.0"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
  <mx:HBox width="100%">
    <mx:VBox height="100%">
      <mx:Label text="My Music"/>
      <mx:DataGrid dragEnabled="true">
        <mx:columns>
          <mx:DataGridColumn headerText="Song Title" dataField="title"/>
          <mx:DataGridColumn headerText="Artist" dataField="artist"/>
        </mx:columns>
        <mx:dataProvider>
          <mx:ArrayCollection>
            <mx:Object songId="0" title="Astronaut" artist="David Byrne" />
            <mx:Object songId="1" title="Rio" artist="Duran Duran" />
            <mx:Object songId="2" title="Enjoy the Silence" artist="Depeche
            Mode" />
            <mx:Object songId="3" title="Mesopotamia" artist="B-52s" />
          </mx:ArrayCollection>
        </mx:dataProvider>
      </mx:DataGrid>
    </mx:VBox>
    <mx:VBox height="100%">
      <mx:Label text="Playlist"/>
      <mx:DataGrid dropEnabled="true">
        <mx:columns>
          <mx:DataGridColumn headerText="Song Title" dataField="title"/>
          <mx:DataGridColumn headerText="Artist" dataField="artist"/>
        </mx:columns>
      </mx:DataGrid>
    </mx:VBox>
  </mx:HBox>

</mx:Application>

When you test this you’ll see two data grids side by side. You can click and drag an element in the My Music data grid, and you’ll see that it creates a copy of that element that moves with the mouse. A small red circle with a white X appears next to the mouse cursor until the mouse is over a drop-enabled component, at which point the red circle becomes a green circle with a white +. If you drop the item anywhere in the application that is not drop-enabled, no further action occurs. However, if you drop the item on a drop-enabled component, the item is added to the drop target at the location where you dropped the object.

The default behavior for drag-enabled components is to allow the user to copy elements from the component. However, you can also allow the user to move elements rather than copy them by setting the dragMoveEnabled property to true. By itself, the dragMoveEnabled property will not have any effect. You must also ensure that dragEnabled is set to true for the component. Example 10-8 uses dragMoveEnabled to create a simple application that allows the user to move an email message from his inbox to the trash.

Example 10-8. A drag-and-drop example that moves items

<?xml version="1.0"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
  <mx:HBox width="100%">
    <mx:VBox height="100%">
      <mx:Label text="Inbox"/>
      <mx:DataGrid dragEnabled="true" dragMoveEnabled="true">
        <mx:columns>
          <mx:DataGridColumn headerText="From" dataField="from"/>
          <mx:DataGridColumn headerText="To" dataField="to"/>
          <mx:DataGridColumn headerText="Subject" dataField="subject"/>
          <mx:DataGridColumn headerText="Date" dataField="date"/>
        </mx:columns>
        <mx:dataProvider>
          <mx:ArrayCollection>
            <mx:Object emailId="0" from="[email protected]" to="[email protected]"
                       subject="Important New Message" date="10/1/2010" />
            <mx:Object emailId="1" from="[email protected]" to="[email protected]"
                       subject="All Items On Sale" date="10/1/2010" />
            <mx:Object emailId="2" from="[email protected]" to="[email protected]"
                       subject="Amazing New Stock" date="10/1/2010" />
            <mx:Object emailId="3" from="[email protected]" to="[email protected]"
                       subject="Blatant Chain Letter" date="10/1/2010" />
          </mx:ArrayCollection>
        </mx:dataProvider>
      </mx:DataGrid>
    </mx:VBox>
    <mx:VBox height="100%">
      <mx:Label text="Trash"/>
      <mx:DataGrid dropEnabled="true">
        <mx:columns>
          <mx:DataGridColumn headerText="From" dataField="from"/>
          <mx:DataGridColumn headerText="To" dataField="to"/>
          <mx:DataGridColumn headerText="Subject" dataField="subject"/>
          <mx:DataGridColumn headerText="Date" dataField="date"/>
        </mx:columns>
      </mx:DataGrid>
    </mx:VBox>
  </mx:HBox>

</mx:Application>

In this example, you can move items only one way: from the inbox to the trash. In a real email application, you’d probably want to allow users to move messages back to the inbox from the trash if they wanted. In such a case, you can simply set dragEnabled and dropEnabled to true for both components.

When you set dragMoveEnabled to true for a component, it makes moving the default behavior. However, if the user holds down the Ctrl key while dragging and dropping, the item will be copied rather than moved. In some cases, that is acceptable behavior. In other cases, you want to ensure that the items always are moved rather than copied. You cannot make such a change via MXML. As we’ll see in the next section, you can use ActionScript and event listeners to handle that behavior.

Understanding Drag-and-Drop Events

When using drag-and-drop functionality, the framework utilizes a handful of events behind the scenes. Understanding those events can be very helpful when you want to modify the default behavior or write completely customized drag-and-drop elements.

Table 10-1 lists all the events, the targets of the events, and what those events mean.

Table 10-1. Drag-and-drop events

Event

Target

Description

mouseDown

Drag initiator

This is usually the event that triggers the start of a drag-and-drop operation.

mouseMove

Drag initiator

In some cases, the drag-and-drop operation does not occur until a mouseMove event.

dragEnter

Drop target

The mouse has entered the drop target while still dragging an object.

dragMove

Drop target

The mouse is moving within the drop target. This is analogous to a mouseMove event, except that it is specific to the drop target while still dragging an object.

dragExit

Drop target

The mouse has moved outside the drop target while still dragging the object.

dragDrop

Drop target

The user has dropped an object on the target.

dragComplete

Drag initiator

This occurs anytime the user drops the object, whether over a drop target or not. You can use the dragComplete event to clean up whatever is necessary for the drag initiator.

The mouseDown and mouseMove events are standard Flash Player events of type flash.events.MouseEvent. The rest of the events are part of the Flex framework, and they are of type mx.events.DragEvent. DragEvent objects have a dragInitiator property, which references the component that originated the drag-and-drop behavior. They also have a dragSource property which is of type mx.core.DragSource, and it contains the data that was copied from the initiator.

DragSource objects have a dataForFormat() method, which requires a string parameter and returns the data that was stored for the format specified by the string parameter. We’ll look at DragSource formats further in the next section. For now, all you need to know is that list-based drag initiators always create DragSource objects with one format called items, and the data returned for that format is an array of all the elements from the data provider of the drag initiator corresponding to the selected items. For example, if the user drags one element from a data grid, the DragSource object will contain an array with just one element.

DragSource objects also have an action property, which reports a value of copy, move, or none. You can use the mx.managers.DragManager COPY, MOVE, and NONE constants for comparison tests. The action property tells you what the expected action is for the drag-and-drop operation. This value is automatically set when using the built-in drag-and-drop behavior of list-based components.

In the preceding section, you saw how there’s no simple MXML-based way to ensure that the user cannot copy elements rather than move them. Using events and ActionScript you can put such safeguards into place, as illustrated in Example 10-9.

Example 10-9. Using ActionScript for drag-and-drop behavior

<?xml version="1.0"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
  <mx:Script>
    <![CDATA[
      import mx.collections.ArrayCollection;
      import mx.controls.DataGrid;
      import mx.events.DragEvent;
      import mx.managers.DragManager;

      private function dragCompleteHandler(event:DragEvent):void {
        if(event.action != DragManager.NONE) {
          var grid:DataGrid = event.dragInitiator as DataGrid;
          var data:ArrayCollection = grid.dataProvider as ArrayCollection;
          var item:Object = event.dragSource.dataForFormat("items")[0];
          data.removeItemAt(data.getItemIndex(item));
        }
      }

    ]]>
  </mx:Script>
  <mx:HBox width="100%">
    <mx:VBox height="100%">
      <mx:Label text="Inbox"/>
      <mx:DataGrid dropEnabled="true" dragEnabled="true"
 dragComplete="dragCompleteHandler(event)">
        <mx:columns>
          <mx:DataGridColumn headerText="From" dataField="from"/>
          <mx:DataGridColumn headerText="To" dataField="to"/>
          <mx:DataGridColumn headerText="Subject" dataField="subject"/>
          <mx:DataGridColumn headerText="Date" dataField="date"/>
        </mx:columns>
        <mx:dataProvider>
          <mx:ArrayCollection>
            <mx:Object emailId="0" from="[email protected]" to="[email protected]"
subject="Important New Message" date="10/1/2010" />
            <mx:Object emailId="1" from="[email protected]" to="[email protected]"
subject="All Items On Sale" date="10/1/2010" />
            <mx:Object emailId="2" from="[email protected]" to="[email protected]"
subject="Amazing New Stock" date="10/1/2010" />
            <mx:Object emailId="3" from="[email protected]" to="[email protected]"
subject="Blatant Chain Letter" date="10/1/2010" />
          </mx:ArrayCollection>
        </mx:dataProvider>
      </mx:DataGrid>
    </mx:VBox>
    <mx:VBox height="100%">
      <mx:Label text="Trash"/>
      <mx:DataGrid dropEnabled="true" dragEnabled="true" dragComplete=
"dragCompleteHandler(event)">
        <mx:columns>
          <mx:DataGridColumn headerText="From" dataField="from"/>
          <mx:DataGridColumn headerText="To" dataField="to"/>
          <mx:DataGridColumn headerText="Subject" dataField="subject"/>
          <mx:DataGridColumn headerText="Date" dataField="date"/>
        </mx:columns>
      </mx:DataGrid>
    </mx:VBox>
  </mx:HBox>

</mx:Application>

You’ll notice that in this example, both data grids have dropEnabled and dragEnabled set to true. This in and of itself allows the user to copy contents from one data grid to the other. However, as we saw in Example 10-8, this does not ensure the type of behavior we require in this case. To achieve the move-only behavior each data grid also listens for dragComplete events. The dragCompleteHandler() method handles the events by either dismissing the event if the event action is set to none, or deleting the element from the drag initiator’s data provider.

Custom Drag-and-Drop Operations

The built-in drag-and-drop functionality will work for many use cases. However, there are also many use cases in which you will want to employ drag-and-drop functionality not supported by the standard, built-in features of the handful of drag-and-drop-enabled components. For these cases, you can create custom drag-and-drop elements.

You can create custom drag-and-drop elements using the events discussed in the preceding section in conjunction with mx.managers.DragManager. The DragManager class has several static methods you can use to handle drag-and-drop functionality.

The doDrag() method allows you to start a drag-and-drop operation. The doDrag() method requires that you specify the following parameters: the drag initiator, a DragSource object specifying the data to copy from the initiator, and the mouse event used to start the drag operation. In addition, in most cases you’ll need to pass it a reference to an object to use as the drag proxy image (the object that actually drags).

Before we can look at an example using doDrag(), we first have to discuss the details of working with DragSource. The DragSource object you pass to doDrag() is what is passed along to event handlers for drag events. This object contains data that you can use when copying, moving, or comparing data. That means you should generally store whatever data you want to pass along to the drag event handlers in the DragSource object. DragSource objects allow you to save many groups of data, each with a unique key (a string value), which is called a format. You can use the addData() method to add data to a DragSource object. The first parameter is the data to store, and the second parameter is the format, which is an arbitrary string:

var dragSource:DragSource = new DragSource();
dragSource.addData(initiator.dataProvider.getItemAt(index), "item");

The DragManager class also dictates the behavior of the drag proxy image when the user moves it over the drop target and when the user drops the object. Normally, the proxy indicates that it cannot be dropped successfully by displaying a small red circle with a white X. You can remove that icon by calling DragManager.acceptDragDrop() and passing it a reference to the drop target for which the user can drop the object. Typically, you call this method in response to a dragEnter event.

Example 10-10 illustrates how to create custom drag-and-drop elements. This simple application uses a column of colored canvases and a grid of canvases with the same colors. The canvases from the column are draggable. When the user drops one of the canvases over the canvas with the same color in the grid, the canvas is removed from the column and the canvas in the grid is lowered in opacity.

Example 10-10. A customized drag-and-drop application

<?xml version="1.0"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
  <mx:Script>
    <![CDATA[
      import mx.core.DragSource;
      import mx.containers.Canvas;
      import mx.events.DragEvent;
      import mx.managers.DragManager;

      private function beginDragAndDrop(event:MouseEvent):void {
        var canvas:Canvas = event.currentTarget as Canvas;
        var dragSource:DragSource = new DragSource();
        var color:uint = canvas.getStyle("backgroundColor");

        // Add the color value to the drag source using the backgroundColor key
        dragSource.addData(color, "backgroundColor");

        // Create the (temporary) canvas that will be dragged
        var proxy:Canvas = new Canvas();
        proxy.width = 50;
        proxy.height = 50;
        proxy.setStyle("backgroundColor", color);

        DragManager.doDrag(canvas, dragSource, event, proxy);
      }

      private function dragEnterHandler(event:DragEvent):void {
        var target:Canvas = event.currentTarget as Canvas;
        var initiator:Canvas = event.dragInitiator as Canvas;

        // If the target and the initiator have the same color
        // then accept the drag drop operation
        if(matches(target, initiator)) {
          DragManager.acceptDragDrop(target);
        }
      }

      private function dragDropHandler(event:DragEvent):void {
        var target:Canvas = event.currentTarget as Canvas;
        var initiator:Canvas = event.dragInitiator as Canvas;

        // If the target and initiator have the same color then
        // remove the initiator and lower the alpha of the target
        if(matches(target, initiator)) {
          vbox.removeChild(initiator);
          target.alpha = .25;
        }
      }

      private function matches(a:Canvas, b:Canvas):Boolean {
        return a.getStyle("backgroundColor") == b.getStyle("backgroundColor");
      }

    ]]>
  </mx:Script>
  <mx:HBox width="100%">
    <mx:VBox id="vbox" height="100%">
      <mx:Canvas width="50" height="50" backgroundColor="#00ff80"
mouseDown="beginDragAndDrop(event)" />
      <mx:Canvas width="50" height="50" backgroundColor="#ff8040"
mouseDown="beginDragAndDrop(event)" />
      <mx:Canvas width="50" height="50" backgroundColor="#80ffff"
mouseDown="beginDragAndDrop(event)" />
      <mx:Canvas width="50" height="50" backgroundColor="#ffff80"
mouseDown="beginDragAndDrop(event)" />
    </mx:VBox>
    <mx:VRule height="213"/>
    <mx:Grid>
      <mx:GridRow width="100%" height="100%">
        <mx:GridItem width="100%" height="100%">
          <mx:Canvas width="50" height="50" backgroundColor="#00ff80"
dragEnter="dragEnterHandler(event)" dragDrop="dragDropHandler(event)" />
        </mx:GridItem>
        <mx:GridItem width="100%" height="100%">
          <mx:Canvas width="50" height="50" backgroundColor="#ff8040"
dragEnter="dragEnterHandler(event)" dragDrop="dragDropHandler(event)" />
        </mx:GridItem>
      </mx:GridRow>
      <mx:GridRow width="100%" height="100%">
        <mx:GridItem width="100%" height="100%">
          <mx:Canvas width="50" height="50" backgroundColor="#80ffff"
dragEnter="dragEnterHandler(event)" dragDrop="dragDropHandler(event)" />
        </mx:GridItem>
        <mx:GridItem width="100%" height="100%">
          <mx:Canvas width="50" height="50" backgroundColor="#ffff80"
dragEnter="dragEnterHandler(event)" dragDrop="dragDropHandler(event)" />
        </mx:GridItem>
      </mx:GridRow>
    </mx:Grid>
  </mx:HBox>


</mx:Application>
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset