Drag and drop is a common user interface technique in desktop applications. It was not so, however, in web applications until the rich Internet applications (RIAs) came along. Flex and Flash Player permit you as a web developer to use drag and drop just as a desktop developer does.
To implement drag and drop in a Flex application, you use the Drag and Drop Manager and the tools it provides. The Drag and Drop Manager enables you to write a Flex application in which users can select an object and then drag it to, and drop it on, a second object. All Flex components support drag-and-drop operations, and a subset has additional drag-and-drop functionality in which implementation requires little more than adding a single property.
In this lesson, you will implement drag and drop in your e-commerce application so a user can click a product and drag it to the shopping cart.
The first step in understanding the Drag and Drop Manager is to learn the terminology surrounding it. The terminology is summarized in the following table.
The following figure gives you a visual representation of the terminology:
There are three phases to a drag-and-drop operation:
Flex components fall into two groups when it comes to drag-and-drop support: those with enhanced drag-and-drop functionality and those without. The following list-based controls have enhanced support for drag and drop:
• <s:List>
• <mx:DataGrid>
• <mx:PrintDataGrid>
• <mx:Tree>
• <mx:Menu>
• <mx:List>
• <mx:HorizontalList>
• <mx:TileList>
What this means to you as a developer is that your life will be a little bit easier when implementing drag and drop with those controls that have enhanced support. In fact, in many cases that might require no more than setting a single property value for each of the controls involved in the drag-and-drop operation.
Your first foray into implementing drag-and-drop operations in Flex will be between two DataGrids. Because they are list-based components and have enhanced drag-and-drop support, you will need to write very little code.
Two properties are important in this first phase: dragEnabled
and dropEnabled
. Here are their descriptions:
• dragEnabled
: Assigned a Boolean value to specify whether the control is allowed to act as a drag initiator (defaults to false
). When it’s true
, the user can drag items from the component.
• dropEnabled
: Assigned a Boolean value to specify whether the control is allowed to act as a drop target (defaults to false
). When it’s true
, the user can drop items onto the control using the default drop behavior.
Stated most simply, you set the dragEnabled
property in the component from which you are dragging to true
, and set the dropEnabled
property in the component on which you are dropping to true
.
So now you will put your drag-and-drop knowledge to use by implementing drag and drop from one DataGrid to another DataGrid.
You will use this project instead of the FlexGrocer one because some of the work in this lesson will not be directly involved with the FlexGrocer site.
Note that the existing code does not use any concepts you have not already learned in this book. The file uses an HTTPService remote procedure call (RPC) to retrieve grocery info. The file then uses a result
handler to place the data into an ArrayCollection, which is then used as a dataProvider
in a DataGrid. When you run the application, you see you have a DataGrid populated with grocery product information and another DataGrid below it. Try to drag and drop between the DataGrids; you will see that this functionality is not yet working.
dragEnabled
property to true
. Run the application; you can click one of the rows in the DataGrid and drag the drag proxy around the screen.Setting this property did two obvious things: It enabled dragging and created the drag proxy, the image attached to the pointer when dragging. Another non-visual event occurred at the same time: A DragSource object was created to hold the data. The data is associated with a format named items
, as the following figure from the debugger shows:
<fx:Script>
block below the existing variable declaration, create a bindable private variable named targetGridDP
of data type ArrayCollection
and assign it a new ArrayCollection. Then bind this variable as the dataProvider
of the second DataGrid, whose id
is targetGrid
.These two steps initialize the dataProvider
of the drop target DataGrid. This means it tells the control what the data type is of the data it will be dealing with. If you do not do this, you will get runtime errors.
dropEnabled
property to true
. Your second DataGrid should appear as follows:You’ve done three basic steps so far to enable drag-and-drop for the application:
• Added the dragEnabled
property to the drag initiator
• Initialized the drop target’s dataProvider
• Added the dropEnabled
property to the drop target
Now you’re ready to test.
Notice that the entire set of data for the row is dragged, not just the visible properties in the DataGrid. The category
column is not displayed in the first DataGrid, but when dropped, that column is displayed in the second DataGrid. This shows you that all the data for the row is in the DragSource, not just the rows that happen to be displayed.
In the description of the dropEnabled
property, the following sentence was used: “When it’s true
, the user can drop items onto the control using the default drop behavior.” So what is this “default drop behavior”? Basically it means that Flex will try to figure out what should be dropped and do what it thinks is best, but that might not be what you want. In the previous exercise it was clear to Flex that when dragging from one DataGrid to another, the columns in the drop target DataGrid should be filled with like-named properties from the DragSource data.
In this task you will drag from a DataGrid to a List component. In this case the “default drop behavior” won’t know what data to drop into the List component and will dump the whole object into the List, which is not what you want.
You will use a drag event to get the data that you want into the List component. Here is a summary of the events for both the drag initiator and the drop target:
Now it is time to get to work.
[object Object]
appear in the List.The default drop behavior did not know what data you wanted placed in the List, so it dropped the whole data object in. Because the List cannot display the entire object, it lets you know what has happened by displaying [object Object]
. The following figure shows the default behavior when dragging from DataGrid to the List.
dragDrop
event listener to the List, and select Generate DragDrop handler to have Flash Builder create the event handler for you. The code generated calls the newly created event handler and passes the event object as a parameter.<s:List id="targetList"
width="200"
dropEnabled="true"
dataProvider="{targetListDP}"
dragDrop="targetList_dragDropHandler(event)"/>
The event is named dragDrop
, and you have no control over that. The function name is created using the instance name of the dispatching object, followed by an underscore, followed by the event name, finally followed by the word Handler
.
<fx:Script>
block.Note that the event is data typed as DragEvent
and the function will not return any data, so the data type is void
.
This function will be called when the user drops the drag proxy onto the List, which is the drop target in this application. Later in this task, you will write code in this function to display just the name of the product in the List.
dgRow
typed as Object. Assign the dgRow
variable the data in the DragSource object associated with the items
format. Use the dataForFormat()
method.var dgRow:Object=event.dragSource.dataForFormat("items");
The dataForFormat()
method is a method of the DragSource class. It retrieves from the DragSource object the data associated with the particular format—in this case, items
.
doDragDrop()
function. You do this by double-clicking in the marker bar just to the left of the line numbers in the editor. You will see a small blue dot appear to indicate the breakpoint was set.The breakpoint will cause Flash Builder to halt execution at the marked line of code, and you will be able to check values of variables. Recall that you first learned about debugging in Lesson 2, “Getting Started.”
dgRow
variable value in the Variables view. You should see that the variable contains all the data from that DataGrid row.The following figure shows the row of data being dragged:
Notice that the variable contains an array of length 1, which means you have only 1 index, which is 0. Also note that the name
property contains the name of the product.
Normally, the Flash perspective is best to work in because you can see so much more of your code.
addItem()
method of the List’s dataProvider
. Remember that the dgRow
variable contained an array of length 1, so use dgRow[0].name
to reference the name.targetList.dataProvider.addItem(dgRow[0].name);
This is a case in which viewing how the data is stored using the debugger is very helpful in retrieving the information.
[object Object]
also appears.The event continued to do what it was supposed to do, even though you displayed some different data; hence, you still see the reference to the object.
preventDefault()
method to cancel the event default behavior.event.preventDefault();
In this case, you can cancel the default behavior. Not all events can be canceled; you must check the documentation for definitive answers on an event-by-event basis. By canceling this event, you prevent the display of [object Object]
in the List.
This wraps up our second task in this lesson on drag and drop.
So far, you have been taking advantage of enhanced functionality in list-based components when it concerns drag and drop. Now it is time to learn how to implement drag and drop on non-enhanced components. In this particular task, the use case is very simple: You want to drag a Label control to a List. Because the Label does not have enhanced drag-and-drop functionality, there is more of a burden on you as the developer to implement it.
Understanding what the list-based components did for you is a good place to start when having to write all the implementation yourself. Here is a list of mechanisms, hidden from you when using the list-based components, that you will need to use when implementing drag and drop without the help of the enhanced components:
• Assign the data to the DragSource object.
• Check to see whether the formats allow dropping onto the drop target.
• Use the data in the drop target (although in the second exercise you did some of this manually).
• Permit the component to be dragged.
• Accept the drop.
Although you have been using the DragSource class up to now in this lesson, you will need to dig deeper into the class when implementing all the functionality yourself. In this exercise, you use the following methods of the DragSource class:
These methods allow you to implement the first three hidden mechanisms. To implement the last two, you need to use methods of the DragManager class:
The doDrag()
method has a number of optional parameters to control the look of the drag proxy. You can find these parameters in the Class documentation for DragManager in the Adobe Flex 4 Language Reference.
Now you’re ready to start writing code for this exercise.
You see you have a Label with the text “Drag me” in it and an empty List below it. At this point, there is no drag-and-drop functionality.
import mx.core.DragSource;
import mx.managers.DragManager;
import mx.events.DragEvent;
import mx.core.IUIComponent;
You could have also just used these classes as data types, and Flash Builder would have imported them for you automatically.
mouseDown
event and have the event call a function named dragIt()
. The function call should pass four parameters; the first is the drag initiator, which in this case is the instance name of the Label: myLabel
. The second parameter is the data you will later place in the DragSource object. In this case, just pass a string of My data here
. The third parameter is the event, which of course is just event
. The last parameter is the format that will be associated with this data. In this task, use myFormat
.mouseDown="dragIt(myLabel,'My data here',event,'myFormat')"
This is the function that will be called to initiate the drag-and-drop operation. You need to pass the parameters because they are all needed in the function to allow:
• Dragging to start
• Placing the data in the DragSource object associated with the format
<fx:Script>
block, create a private function named dragIt()
, which returns void
. The function should accept four parameters that, of course, correspond to the data passed to the function. Use the names and data types shown here:initiator:Label
dsData:String
event:MouseEvent
format:String
Of these parameters, the initiator
could be any kind of component, and the dsData
could be nearly any kind of data you want to be dragged from one component to another. The event
will often be the mouseDown
MouseEvent or the mouseMove
event, but that would not change either the event
parameter name nor the data type used here. The format
will always be a String.
ds
typed as a DragSource
and set it equal to a new DragSource object.var ds:DragSource=new DragSource();
This creates the DragSource object that will have data added to it.
addData()
method of the ds
DragSource object to add the data passed in the dsData
parameter to the ds
object. Associate it with the format passed in the format
parameter.ds.addData(dsData,format);
An important point here is that you can store data associated with multiple formats, which means you can use multiple addData()
methods on the same DragSource object. You might want to do this if you have multiple drop targets and want to drop different data in each drop target. The different drop targets would use different arguments in the dataForFormat()
method to get the appropriate data.
doDrag()
method of the DragManager class. You pass it the three arguments initiator
, ds
, and event
. Check to make sure your completed function appears as shown here:Remember that a static method is one you can invoke directly from the class without first instantiating it.
You now move on to coding the List to accept the drop of the Label and to display the data passed in the DragSource in the List.
dragEnter
event and have it call a function named doDragEnter()
.The function should pass two parameters. The first is the event
, and the second is the format—which in this case should match the format used earlier: myFormat
.
dragEnter="doDragEnter(event,'myFormat')"
You are passing data to the function that allows the initiator, the Label, to be dragged to the drop target, the List.
<fx:Script>
block, create a private function named doDragEnter()
, which returns void
. The function should accept two parameters. Name the first parameter event
, typed as a DragEvent
, and the second parameter format
, typed as a String
.private function doDragEnter(event:DragEvent,format:String):void
{
}
Both these parameter values are needed to allow the dropping of the initiator.
if
statement that checks to see whether the formats of the two objects match. Use the hasFormat()
method of the DragSource object, which is contained in the event object. The argument of the hasFormat()
method should be the format
parameter passed to the function. Remember the hasFormat()
method returns true
if the DataSource object contains a matching format of the drop target; otherwise, it returns false
.if(event.dragSource.hasFormat(format)){
}
The List is looking in the DragSource object and seeing whether a format exists that matches one of the formats it is allowed to accept. The hasFormat()
function will return either true
or false
.
hasFormat()
function returns true
, use the DragManager’s static function of the acceptDragDrop()
method to allow the dropping. The argument of the function should be the List itself, which is best referred to in this case as event.target
.DragManager.acceptDragDrop(event.target);
You could have actually replaced event.target
with the instance name of the List, myList
, and the function would have had the same result. The advantage of using the more generic event.target
is that it makes this function more reusable. You can use the function for any dragEnter
result handler—it will work correctly.
acceptDragDrop()
method is defined to accept an object of type IUIComponent. For this reason you need to cast event.target
as an IUIComponent to satisfy the compiler.The IUIComponent class defines the basic set of APIs that must be implemented to be a child of a Flex container or list.
private function doDragEnter(event:DragEvent,format:String):void{
if( event.dragSource.hasFormat( format )){
DragManager.acceptDragDrop( event.target as IUIComponent );
}
}
You should now be able to drag the Label. When it moves over the List, the red X
disappears, and you can drop the drag proxy. At this point, nothing happens when you do the drop.
dragDrop
event and have it call a function named doDragDrop()
. The function should pass two parameters, the event
and the format, which in this case should match the format used earlier: myFormat
.dragDrop="doDragDrop(event,'myFormat')"
You are passing the data needed to have the data retrieved from the DragSource and have it displayed in the List.
<fx:Script>
block, create a private function named doDragDrop()
, which returns void
. The function should accept two parameters. Name the first parameter event
, data typed as DragEvent
, and the second parameter format
, typed as String
.You need the event object in this function because it contains the DragSource object, and that is where the data is stored. Remember that you stored the String My data here
in the DragSource object in steps 3–6 of this exercise. The format is needed because that is how you pull data from the DragSource object using the dataForFormat()
method.
myLabelData
, typed as Object
. Assign this variable the data being dragged by using the dataForFormat()
function to retrieve the data from the dragSource
property of the event object. The argument of the function should be the format
parameter passed to the function.var myLabelData:Object=event.dragSource.dataForFormat(format);
Remember that you can store data associated with multiple formats, so you must specify which format’s data to retrieve when retrieving data.
addItem()
method on the List’s dataProvider
property to do this.myList.dataProvider.addItem(myLabelData);
You have achieved your goal of moving the Label’s data into the List.
private function doDragDrop(event:DragEvent,format:String):void{
var myLabelData:Object=new Object();
myLabelData=event.dragSource.dataForFormat(format);
myList.dataProvider.addItem(myLabelData);
}
Now when you drag the Label to the List, you will see that the data from the Label, the String “My data here” is displayed in the List. The following figure shows the List after successfully dropping the Label data.
Now that you have a solid background in drag and drop, you will implement drag-and-drop functionality in the e-commerce application of FlexGrocer.
The culmination of your work in this lesson is to implement dragging a grocery item into the shopping cart. The exercises you have performed so far in this lesson have prepared you well for this final exercise; in fact, some of the code you have already written will be copied and pasted for use in this exercise.
In these steps, you will enable the user to click the grocery item, drag it to the small shopping cart, and then drop it in. The grocery item is displayed in a VGroup container, and the shopping cart is a List. Because the VGroup is not a drag-and-drop-enhanced component, you will have to pattern your code here after what you just wrote in the section “Using a Non-Drag-Enabled Component in a Drag-and-Drop Operation.”
Alternatively, if you didn’t complete the previous lesson or your code is not functioning properly, you can import the FlexGrocer.fxp project from the Lesson13/start folder. Please refer to Appendix A for complete instructions on importing a project should you ever skip a lesson or if you ever have a code issue you cannot resolve.
This is the component in which the grocery data is displayed; so this is where you will have to permit the data to be dragged.
<s:VGroup>
just below the <fx:Declarations>
tag set. In that container, locate the <mx:Image>
tag that displays the product. Add an id
property to the Image tag and assign it the value img
.You will be referencing this image several times and need to give it an instance name.
mouseDown
event in the Image tag and select Generate DragDrop handler to have Flash Builder create the event handler for you. Note that the event object was automatically passed. Add a second parameter, which is the format that will be associated with this data. In this task, use the string cartFormat
.mouseDown="img_mouseDownHandler(event,'cartFormat')"
By placing the mouseDown
event on the Image, it will enable the user to start the drag process by clicking the image.
<fx:Script>
block and remove the TODO comment. Add a second parameter named format
typed as a String
.protected function img_mouseDownHandler(event:MouseEvent,format:String):void{
}
This function has two main purposes: to get data into the object being dragged and to permit the component to be dragged.
dragSource
and assign it a new DragSource object. Next, use the addData()
method to associate the newly created object with the product and format.var dragSource:DragSource=new DragSource();
dragSource.addData(product,format);
If you used code-completion, DragSource was imported for you. Else, import mx.core.DragSource manually. Remember that the addData()
method’s two parameters assign the data and the format to the DragSource object. In this case the data is the product being displayed in the VGroup, and the format is the format string passed to the event handler.
doDrag()
method of the DragManager class. Recall that you must pass the method three parameters, the initiator, the DragSource object, and the event. In this case the initiator is event.target as IUIComponent
. The DragSource is the dragSource
object you created. Lastly, the event is the event
parameter passed to the event handler.DragManager.doDrag(event.currentTarget as IUIComponent, dragSource, event);
If you used code-completion, IUIComponent and DragManager were imported for you. If not, import mx.core.IUIComponent and mx.managers.DragManager now. You had to cast the initiator as IUIComponent because event.currentTarget
is typed as an Object by default, which is not a visual component and hence cannot be dragged.
You see the drag proxy is the outline of the Image, or a rectangular box. Later in this task, you will change the drag proxy to the image of the grocery item.
At this point there is no drop target, so you cannot drop the data anywhere.
This file contains the List that is your shopping cart to which grocery items are dragged.
id
of cartList
and add a dragEnter
event to the List. Select Generate DragDrop handler to have Flash Builder create the event handler for you. In addition to the event object automatically passed, add a second parameter of the String cartFormat
.dragEnter="cartList_dragEnterHandler(event,'cartFormat')"
doDragEnter()
function and paste it in the newly generated event handler. Also add a second parameter named format
, typed as a String
. Be sure your handler appears as shown here.private function cartList_dragEnterHandler(event:DragEvent,format:String):void{
if(event.dragSource.hasFormat( format )){
DragManager.acceptDragDrop( event.target as IUIComponent );
}
}
This function has only one purpose: to check whether formats enable the drag initiator to be dropped. The if
statement determines whether there are matching formats; then the acceptDragDrop()
method allows the actual dropping to take place.
mx.managers.DragManager
and mx.core.IUIComponent
classes.These classes are used in the function you just copied into the file, and therefore not automatically imported.
X
disappear, and you can drop the drag proxy.At this point, nothing happens when you drop the drag proxy.
id
of cartList
and add a dragDrop
event to the List. Select Generate DragDrop handler to have Flash Builder create the event handler for you. In addition to the event object automatically passed, add a second parameter of the String cartFormat
.dragDrop="cartList_dragDropHandler(event,'cartFormat')"
You pass the information to this function, which will place data in the shopping cart.
format
typed as String
.protected function cartList_dragDropHandler(event:DragEvent, format:String):void
{
}
product
, typed as a Product
. Assign product
the result of calling the dataForFormat()
method of the event.dragSource object, passing the format as an argument. You will need to cast the result as a Product.var product:Product = event.dragSource.dataForFormat( format ) as Product;
This Product object is needed to create a ShoppingCartItem in the next step of the task. The dataForFormat()
method retrieves data based on the format used as the argument of the function. In this case, the data stored in the DragSource object was the product data added in step 6 of this task using the addData()
method.
shoppingCartItem
, typed as a ShoppingCartItem
. Assign that variable equal to a new ShoppingCartItem. The arguments of the ShoppingCartItem constructor should be the Product
object created in the last step and the number 1
.var shoppingCartItem:ShoppingCartItem = new ShoppingCartItem(product,1);
Here is a quick review of how the Product object got in the DragSource:
• In steps 4, 5 and 6 of this exercise, you passed a Product object to the img_mouseDownHandler()
function.
• The function placed the Product object into the DragSource object using the addData()
method and associated it with the cartFormat
format.
• In the event handler just created, you retrieved that same Product object and will now place it in the shopping cart.
addItem()
method of the shoppingCart
object and pass the shoppingCartItem
variable as a parameter.Check to be sure your function appears as shown here:
protected function cartList_dragDropHandler(event:DragEvent, format:String):void{
var product:Product = event.dragSource.dataForFormat( format ) as Product;
var shoppingCartItem:ShoppingCartItem = new ShoppingCartItem(product,1);
shoppingCart.addItem( shoppingCartItem );
}
The method invocation actually places the ShoppingCartItem object in the shopping cart.
You see that the drag-and-drop operation is working, but the drag proxy is the default proxy for the Image. In the next step you add code so the drag proxy becomes the image of the grocery item.
<fx:Script>
block, locate the img_mouseDownHandler()
function. At the top of the function create a variable local to the function named proxy
of type BitmapImage
. Assign the newly created variable a new BitmapImage
object. As the next line of code in the function, assign proxy.source
the value img.content
.var proxy:BitmapImage = new BitmapImage();
proxy.source = img.content;
Here you are creating a new image to act as the proxy. This code may cause you to have some questions.
Your first question might be, why not just use the existing Image object as the proxy? This is because by default the drag-and-drop operation removes the drag proxy from its source. You could have simply used the existing Image as the drag proxy, but after dragging and dropping, the image would no longer be shown with the other grocery item data.
The next question could be, what is a BitmapImage and why use it instead of another Image instance? Actually you could have used another Image instance, but it would not be the best choice. The Image tag is quite complex and performs many functions, including retrieving an actual image. This makes the component quite “heavy.” The BitmapImage only has the ability to display data that is already loaded and is therefore a perfect fit for this implementation since the Image tag has already loaded the actual image. BitmapImage is much more svelte than the Image tag, so a better choice in this case.
Finally, it may seem odd that you assign the source
property of the BitmapImage the value of the content
property of the Image tag. Why not just assign the source
property of Image to the source
property of BitmapImage? This, again, goes back to the functionality of the Image tag. You specify a source
in the Image tag, which is the path to the object to load. The Image tag then places the retrieved image in the content
property. The BitmapImage uses the source
property as the actual image, not an object to retrieve as does the Image tag, hence proxy.source = img.content
.
DragManager.doDrag()
method invocation, add a fourth parameter of proxy
.DragManager.doDrag( event.target as IUIComponent, dragSource, event, proxy);
This fourth parameter represents the dragImage
. Instead of the outline of the Image of the grocery item data being the drag proxy, you have now specified that the image of the item should be displayed when dragging is taking place.
Implicit coercion of a value of type spark.primitives:BitmapImage to an unrelated type mx.core:IFlexDisplayObject;
The fourth parameter, the drag proxy, must implement the interface IFlexDisplayObject
to be a valid drag proxy. BitmapImage does not do this, hence the error. You will solve this issue in the next step.
imageHolder
, data typed as a Group
, and assign it a new Group object. Next use the addElement()
method of the imageHolder
Group object and pass as a parameter the proxy
object.var imageHolder:Group = new Group();
imageHolder.addElement(proxy);
Be sure to either use code-completion or manually import spark.components.Group. Since the Group class does implement IFlexDisplayObject
, you will place the BitmapImage in a Group so it can be used as a drag proxy.
DragManager.doDrag()
method invocation, change the fourth parameter from proxy
to imageHolder
.DragManager.doDrag(event.currentTarget as IUIComponent, dragSource, event, imageHolder);
After this change, save the file and you will see the error is no longer in the Problems view.
img_mouseDownHandler()
function appears as follows, and then run the application. You should be able to drag the image of the grocery item and drop it in the cart.In this lesson, you have:
• Implemented drag-and-drop operations between two drag-enabled components and used the default drop process (pages 329–332)
• Implemented drag-and-drop operations between two drag-enabled components and customized the drop process to use the data stored in the DragSource object (pages 333–336)
• Implemented drag-and-drop operations between non-drag-enabled components and used a custom dragImage
(pages 337–343)
• Implemented drag-and-drop operations in the shopping cart (pages 343–349)