You are currently viewing a snapshot of www.mozilla.org taken on April 21, 2008. Most of this content is highly out of date (some pages haven't been updated since the project began in 1998) and exists for historical purposes only. If there are any pages on this archive site that you think should be added back to www.mozilla.org, please file a bug.



Drag and Drop

Feature Owner
Mike Pinkerton

Overview

This document describes the Drag and Drop Service used to transfer data with mozilla and other applications via drag and drop and assumes the reader is familiar with the Transferable object discussed in the previous document. The meat of this API can be found in nsIDragService and nsIDragSession.

The Drag Service, whose interface is nsIDragService, always exists. Its purpose is to start a drag (either from within the app or from some external source) and report if a drag is currently in progress. When a drag is underway, the Drag Service creates (and manages) a Drag Session, whose interface is nsIDragSession. It is the Drag Session that holds all of the information relevant to the current drag. Drag Sessions are transient, and the lack of its existance means that there is currently no drag in progress.

Handling Drag Events

Initiating a Drag From Within The App

When the user makes a gesture that could be interpreted as a drag (a click and motion of the mouse over some number of pixels), a NS_DRAGDROP_GESTURE event is generated and sent to the content model starting at the node where the mouse initially was depressed. While this event can be handled at the frame level in C++, we recommend that you handle it in JavaScript with an ondraggesture handler.

This event will bubble up the content tree as necessary so you don't need to worry about placing a handler at every single level. The target field of the event structure (event.target) will contain the content node where the mouse went down so you can still tell what happened as the event bubbles.

Below is an example of the XUL and the JS:

<titledbutton id="page-proxy-button" ondraggesture="DragProxyIcon(event);"/>
function DragProxyIcon ( event )
{
  // get the drag service
  var dragStarted = false;
  var dragService =
    Components.classes["component://netscape/widget/dragservice"].getService(Components.interfaces.nsIDragService);
  if ( dragService ) {

    // create a transferable
    var trans =
      Components.classes["component://netscape/widget/transferable"].createInstance(Components.interfaces.nsITransferable);
    if ( trans ) {
      trans.addDataFlavor("text/unicode");
      var genTextData =
        Components.classes["component://netscape/supports-wstring"].createInstance(Components.interfaces.nsISupportsWString);
      if ( genTextData ) {
        genTextData.data = "this is a test;

        // add data to the transferable
        trans.setTransferData ( "text/unicode", genTextData, id.length * 2 );  // double byte data

        // create an array for our drag items, though we only have one this time
        var transArray =
          Components.classes["component://netscape/supports-array"].createInstance(Components.interfaces.nsISupportsArray);
        if ( transArray ) {
          // put it into the list as an |nsISupports|
          var genTrans = trans.QueryInterface(Components.interfaces.nsISupports);
          transArray.AppendElement(genTrans);

          // Actually kick off the drag
          var nsIDragService = Components.interfaces.nsIDragService;
          dragService.invokeDragSession ( transArray, null, nsIDragService.DRAGDROP_ACTION_COPY +
                                              nsIDragService.DRAGDROP_ACTION_MOVE );
          dragStarted = true;
        }
      } // if data object
    } // if transferable
  } // if drag service

  if ( dragStarted )          // don't propagate the event if a drag has begun
    event.preventBubble();

} // DragProxyIcon

The key call here is dragService.invokeDragSession(), which actually tells the OS to begin the drag. Note that just because you receive a draggesture event this does not mean that a drag should begin. For example, if there is no selection in a text field, obviously a click and drag will select text and should not start a drag. Only the client has enough information to make the decision so it is completely up to the client to tell the Drag Service (and subsequently the OS) to go ahead with the drag.

Discuss the drag region parameter

Futhermore, it is very important that if your handler starts the drag that it does not allow the event to continue to bubble. Not stopping the event will cause any other handlers further up in the tree to fire and possibly invoke the drag multiple times. To prevent this, make sure you call event.preventBubble().

When The Drag Starts Outside The App

Obviously, not all drags begin with clicks within our application, yet we still need to have a Drag Session created for communicating with the OS about the information contained in the drag. The native implementations of drag and drop should call the following API on the Drag Service when they notice a drag has entered or left a gecko window:

/**
  * Tells the Drag Service to start a drag session. This is called when
  * an external drag occurs
  */
void startDragSession ( ) ;

/**
  * Tells the Drag Service to end a drag session. This is called when
  * an external drag occurs
  */
void endDragSession ( ) ;

These calls create a Drag Session filled with the appropriate information. No draggesture events will be generated for drags started this way which is why it is up to native C++ code to read the OS drag events appropriately. Clients should not have to worry about this case, but platform implementers will.

While The Mouse Moves Around

As the user moves their mouse within the app there are several events that get generated: dragenter, dragexit, and dragover (which map to NS_DRAGDROP_ENTER, NS_DRAGDROP_EXIT, and NS_DRAGDROP_OVER, respectively). For most purposes, the only one that you need to worry about when doing tracking is dragover which is called while the mouse is over a frame. In order to get the correct feedback, you need to write an ondragover handler

This event will bubble up the content tree as necessary so you don't need to worry about placing a handler at every single level. The target field of the event structure (event.target) will contain the content node the mouse is currently over so you can still tell what is going on as the event bubbles.

<box id="appcontent" align="vertical" flex="100%"
    ondragover="return DragOverContentArea(event);">
function DragOverContentArea ( event )
{
  var validFlavor = false;
  var dragSession = null;

  var dragService =
    Components.classes["component://netscape/widget/dragservice"].getService(Components.interfaces.nsIDragService);
  if ( dragService ) {
    dragSession = dragService.getCurrentSession();
    if ( dragSession ) {
      if ( dragSession.isDataFlavorSupported("moz/toolbaritem") )
        validFlavor = true;
      else if ( dragSession.isDataFlavorSupported("text/unicode") )
        validFlavor = true;
      //XXX other flavors here...such as files from the desktop?

      if ( validFlavor ) {
        // XXX do some drag feedback here, set a style maybe???

        dragSession.canDrop = true;
        event.preventBubble();
      }
    }
  }
} // DragOverContentArea

The main purpose of this handler is to check if the correct flavors are present in the drag (using dragSession.isDataFlavorSupported()) and set the canDrop attribute on the Drag Session. Setting this attribute lets the OS know that the drop is allowed and can do things like change the cursor to indicate this area is a valid drop target.

The above handler is fairly simplistic. While all ondragover handlers follow this basic pattern, when dragging over toolbars or trees other actions are necessary in order to get the correct drop feedback these widgets provide. See the toolbar and tree documentation for how to write the best ondragover handlers for these widgets.

Again, it is very important that if your handler determines that the drag is allowable that it does not allow the event to continue to bubble. Not stopping the event will cause any other handlers further up in the tree to fire and possibly change the answer/feedback you've so carefully computed. To prevent this, make sure you call event.preventBubble().

When A Drop Occurs

When the mouse is released over a valid drop target (and the Drag Session's canDrop attribute is true), a NS_DRAGDROP_DROP event is sent to the content where the drop occured. This can be handled with an ondragdrop event handler.

This event will bubble up the content tree as necessary so you don't need to worry about placing a handler at every single level. The target field of the event structure (event.target) will contain the content node where the mouse edned up so you can still tell what happened as the event bubbles.

Below is an example of the XUL and the JS:

<box id="appcontent" align="vertical" flex="100%"
    ondragdrop="return DropOnContentArea(event);">
function DropOnContentArea ( event )
{
  var dropAccepted = false;

  var dragService =
    Components.classes["component://netscape/widget/dragservice"].getService(Components.interfaces.nsIDragService);
  if ( dragService ) {
    var dragSession = dragService.getCurrentSession();
    if ( dragSession ) {
      var trans =
        Components.classes["component://netscape/widget/transferable"].createInstance(Components.interfaces.nsITransferable);
      if ( trans ) {
        trans.addDataFlavor("text/unicode");
        for ( var i = 0; i < dragSession.numDropItems; ++i ) {
          dragSession.getData ( trans, i );
          var dataObj = new Object();
          var bestFlavor = new Object();
          var len = new Object();
          trans.getAnyTransferData ( bestFlavor, dataObj, len );
          if ( dataObj )
            dataObj = dataObj.value.QueryInterface(Components.interfaces.nsISupportsWString);
          if ( dataObj ) {
            // pull the URL out of the data object
            var id = dataObj.data.substring(0, len.value / 2);
            event.preventBubble();
          }
        } // foreach drag item
      }
    }
  }
} // DropOnContentArea

There isn't really too much involved in this handler as it looks a lot like the normal code written when accessing data using a transferable object. The drag specific piece in this example is the numDropItems attribute which contains the number of discrete items that form the drag. The for loop in the example just asks the Drag Service for each item in turn using dragSession.getData().

For clients that are doing their own drop feedback, it is important to point out that an exit event will be generated after the drop is processed to allow for any kind of cleanup (erasing the drop feedback, perhaps). This guarantees that every drag enter will be matched with a corresponding drag exit event.

Once again, it is very important that if your handler handles the drop that it does not allow the event to continue to bubble. Not stopping the event will cause any other handlers further up in the tree to fire and possibly process the drop twice. To prevent this, make sure you call event.preventBubble().