"Drag and drop" is the term applied to the action of dragging an object from one place to another. A "drag" begins by pressing down on an eligible object and moving it while applying pressure (either by keeping a finger in touch with the screen or pressing down on a mouse button). The "drop" happens when the pressure is released (e.g., letting go of the mouse button). If the drop happens over a suitable place (the "drop target"), then the item being dragging is incorporated into the drop target somehow. A drag and drop can either cause the initial object to move to the new location or a copy of the initial object added to the new location.
Drag and Drop in FlexJS is accomplished using beads. When you add the beads to components, you enable them to have some part of them to be dragged and/or act as a drop target. This document will show you how to add the code necessary to make it all work.
DragMouseController
The DragMouseController is the bead you add to a component strand to activate dragging. This will not, by itself, cause anything to be dragged. What it will do is dispatch events which you can catch and use.
<js:Group id="bucketGroup">
<js:beads>
<js:DragMouseController dragStart="handleDragStart(event)" />
</js:beads>
<!-- components in the group -->
</js:Group>
In this example, the DragMouseController will dispatch a "dragStart" event when the user has pressed down on something in the "bucketGroup" and moved the mouse enough to indicate a drag. The event handler, handleDragStart() needs to do three things:
- Determine which item is being dragged. This is most likely the event.target - one of the members of the Group.
- Set the DragEvent.dataSource with the data that represents the item being dragged.
- Set the DragMouseController.dragImage to a component that represents the item being dragged. Most often this will be a similar component or one made to look like it.
DragEvent.dataSource = (event.target as MyObject).data;
var dragImage:Label = new Label();
dragImage.text = (event.target as MyObject).toString();
DragMouseController.dragImage = dragImage;
In this example the event.target is cast to a class called "MyObject" which has a data property and that becomes the dragSource. Then a dragImage is created as a simple Label with the Label text being a string representation of the object selected. You can make any UI component, or component composite, a dragImage.
Once that has been set up in the "dragStart" event handler, the DragMouseController bead takes over and displays the dragImage as you move around the screen.
The DragMouseController also dispatches several other events you may find useful:
"dragMove" - this event is sent while the object is being dragged around. The event.target will be whatever the mouse is over at the time the event has been dispatched.
"dragEnd" - this event is sent when the drag is over. This happened because the user released the mouse button; a "dragDrop" (see below) may also occur, but "dragEnd" simply means the drop operation has concluded.
DropMouseController
Use DropMouseController with the component strand that will accept the drop from the drag source strand. This can be the same component if you want to use drag and drop to re-arrange a strand's children. The DropMouseController emits its own events, such as "dragEnter" and "dragDrop". You can use these events to provide feedback to the user (such as adding a border when the drag image enters the drop target space).
The "dragDrop" event is the event to handle when the user releases the mouse over the strand with the DropMouseController bead. You use this event to incorporate the data being dragged into the target component.
<js:Group id="dropZone">
<js:beads>
<js:VerticalLayout />
<js:DropMouseController dragDrop="handleDrop(event)" />
</js:beads>
</js:Group>
The event listener for the DropMouseController's "dragDrop" event can do whatever it needs to do to incorporate the data.
private function handleDrop(event:DragEvent):void
{
var data:Object = DragEvent.dataSource;
var newItem:OtherObject = new OtherObject(data);
dropZone.addElement(newItem);
dropZone.dispatchEvent(new Event("layoutNeeded"));
}
In this example the dropped data is used to create a new instance of "OtherObject" and it is added as an element to the dropZone Group. Notice that the dropZone group is told to update its layout after the new item is added; FlexJS will not automatically run layouts.
The DropMouseController also dispatches several other events you may find useful:
"dragEnter" - this event is sent when the mouse, in the drag state, has entered the drop zone space. You might, for example, highlight the border of the drop zone.
"dragExit" - this event is sent when the mouse, in drag state, has exited the drop zone space.
"dragOver" - this event is sent in conjunction with "dragMove" (from the DragMouseController) while the mouse is moving around within the drop zone.
Drag Initiator
Once the drop has completed, its often useful to the source component to know that the drop was accepted. Perhaps you will want to delete the original component or change it somehow to reflect the drag and drop action. You do this by implementing the IDragInitiator interface and setting the object into the DragMouseController:
// code from DragMouseController example, including...
DragMouseController.dragImage = dragImage; // see above
DragMouseController.dragInitiator = this;
Whatever the component class is that has these two Groups becomes the drag initiator, implementing IDragIntiator interface. This means the class will need to implement two functions:
public function acceptingDrop(dropTarget:Object, type:String):void
{
// maybe highlight the component that was dragged.
}
public function acceptedDrop(dropTarget:Object, type:String):void
{
// maybe delete the component that was dragged.
}
The dropTarget parameter in each function just lets you know what object accepted the drop - maybe you will have different drop targets and will react differently depending on where the drop happens. The type is an indicator of what was dropped. You should save some way to identify the drag source in the "dragStart" event.
DataContainer and List Components
Very often a List is used with drag and drop; either to move or copy items from one list to another or to re-arrange items in one list. FlexJS provides specialty beads for Lists.
SingleSelectionDragSourceBead can be used with Lists whose items are to be dragged around. This bead creates a DragMouseController and listens to its events. This bead also implements IDragInitiator.
SingleSelectionDragImageBead can be used with Lists as a quick way to display a drag image from the list's itemRenderer that is being dragged about.
SingleSelectionDropTargetBead can be used with Lists that are accepting drops. This can be the same list that uses SingleSelectionDragSourceBead to allow for items to be re-arranged. This bead creates a DropMouseController and listens to its events.
SingleSelectionDropIndicatorBead can be used with Lists in conjunction with SingleSelectionDropTargetBead and provides visual feedback of where a drop is likely to occur. That is, it draws a thick black line above or below the item renderers in a list to show where the drop will insert the data being dragged. This bead is optional and if not present, no drop feedback will be given.
<js:List>
<js:beads>
<js:SingleSelectionDragSourceBead />
<js:SingleSelectionDragImageBead />
<js:SingleSelectionDropTargetBead />
<js:SingleSelectionDropIndicatorBead />
<!-- beads for the data provider -->
</js:beads>
</js:List>
By having these four beads present, the user will now be able to drag one item from the list to a new location within the same list. When the user presses down on an item renderer, the SingleSelectionDragSourceBead will respond by checking to see if there is a drag image; the SingleSelectionDragImageBead will have responded by creating a simple Group+Label as the drag image using the row's data and toString() function; see below to customize the drag image.
As the user drags, the SingleSelectionDropTargetBead is receiving the "dragOver" and "dragEnter" events. While these events transpire, the SingleSelectionDropIndicatorBead presents a black line between row; see below to customize the drop indicator. This bead does not respond to those, but you could create derivations of it that would respond.
When the user finally drops, the SingleSelectionDropTargetBead will be notified by its DropMouseController and adds the data from the drag source to the list's data provider. Once the data is incorporated, the IDragInitiator, the SingleSelectionDragSourceBead, is called upon to remove the original data unless SingleSelectionDragSourceBead's dragType property was set to "copy".
- These four beads can also be used with DataGrid in exactly the same way as List or DataContainer.
- Pair SingleSelectionDragSourceBead with SingleSelectionImageBead since the image you want to see is that of the item being dragged.
- Pair SingleSelectionDropTargetBead and SingleSelectionDropIndicatorBead to provide the best experience.
- Drag between lists by putting SingleSelectionDropTargetBead in another List.
Customizing the Drag Image
When you use the basic DragMouseController and DropMouseController beads, you have to create the drag image yourself. This can be as simple as Label or something more complex; the SingleSelectionDragImageBead creates a Group with a Label child, for example.
When you want to customize the drag image for list or grid drag and drop, you do so by creating a class that extends SingleSelectionDragImageImage and override its protected function, createDragImage(). This function should return a UIBase component. Then you can substitute your custom bead for the <js:SingleSelectionDragImageBead />.
Customizing the Drop Indicator
The SingleSelectionDropIndicatorBead creates a Rect component and fills it with black. The SingleSelectionDropTargetBead takes care of placing an sizing it. If you want to present some other than a thick black bar, you can extend SingleSelectionDropIndicatorBead and override its getDropIndicator() function.
public function getDropIndicator(ir:Object, width:Number, height:Number):UIBase
The function takes three arguments: ir is the item over which the drag start has begun. This is usually an item renderer for a list or grid (this value is ignored for the default SingleSelectionDropIndicatorBead but is present to help with any customizations you may want to do). The width and height are suggestions or preferred sizes. You can create your UIBase custom component and size it as you see fit and return that as the drop indicator. Once the object has been constructed, only its (x,y) position will be changed. Each "dragStart" will cause this function to be invoked.