Drag and Drop Example

Gecko 1.9.1 (Firefox 3.5) and later supports a newer and simpler API.

An example of implementing drag and drop will be implemented in this section.

Dragging Elements Around

Here, we'll create a simple board where items from a palette can be dragged onto the board. The user can click on one of several XUL elements on the palette and drag it onto a stack element to create an element of a particular type.

First, we'll add the wrapper scripts:

<script src="chrome://global/content/nsDragAndDrop.js"/>
<script src="chrome://global/content/nsTransferable.js"/>
<script src="dragboard.js"/>

An additional script file dragboard.js is included which will contain the code we will write ourselves.

The board will be created using a stack element. We'll use some style properties to set the width and height of the stack. A maximum size is also specified so that it doesn't resize when new elements are dragged onto it.

The board will need to respond to the dragdrop event so that an element is created when the user drags onto it.

<stack id="board"
       style="width:300px; height: 300px; max-width: 300px; max-height: 300px"
       ondragover="nsDragAndDrop.dragOver(event, boardObserver)"
       ondragdrop="nsDragAndDrop.drop(event, boardObserver)">
</stack>

The board only needs to respond to the dragdrop and dragover events. We'll add a boardObserver to the file dragboard.js in a moment.

Next, a palette will be added to the right side of the window. It will contain three buttons, one to create new buttons, one to create check boxes and the other to create textboxes. This buttons will respond to the draggesture event and start a drag.

<vbox>
  <button label="Button"
          elem="button"
          ondraggesture="nsDragAndDrop.startDrag(event, listObserver)"/>
  <button label="Check Box"
          elem="checkbox"
          ondraggesture="nsDragAndDrop.startDrag(event, listObserver)"/>
  <button label="Text Box"
          elem="textbox"
          ondraggesture="nsDragAndDrop.startDrag(event, listObserver)"/>
</vbox>

The nsDragAndDrop object will be called to do most of the work. We'll create a listObserver object that will set the data to be dragged. Note that each button here has an additional elem attribute. This is a made-up attribute. XUL doesn't handle it and just ignores it, but we can still retrieve it with the DOM's getAttribute function. We need this so that we know what type of element to create when dragging.

Next, we'll define the two listener objects. First, the listObserver which needs a function to handle the start of the drag.

var listObserver = {
  onDragStart: function (event, transferData, action) {
    var txt = event.target.getAttribute("elem");
    transferData.data = new TransferData();
    transferData.data.addDataForFlavour("text/unicode", txt);
  }
}

One function has been defined, onDragStart, which will be called by the nsDragAndDrop object when necessary. The function adds the data to be dragged to the transfer object. The elem attribute is retrieved from the target of the drag event. The target will be the element that had the drag start on. We'll use the value of this attribute as the data of the drag.

The boardObserver will need three functions, getSupportedFlavours, onDragOver and onDrop. The onDrop function will grab the data from the drag session and create a new element of the appropriate type.

var boardObserver = {
  getSupportedFlavours : function () {
    var flavours = new FlavourSet();
    flavours.appendFlavour("text/unicode");
    return flavours;
  },

  onDragOver: function (event, flavour, session) {},

  onDrop: function (event, dropdata, session) {
    if (dropdata.data != "") {
      var elem = document.createElement(dropdata.data);
      event.target.appendChild(elem);
      elem.setAttribute("left", "" + event.pageX);
      elem.setAttribute("top", "" + event.pageY);
      elem.setAttribute("label", dropdata.data);
    }
  }
}

The getSupportedFlavours function needs only to return a list of flavours that the stack can accept to be dropped on it. In this case, it only accepts text. We don't need to do anything special for the onDragOver function, so no code is added in its body.

The onDrop handler first uses the createElement function to create a new element of the type stored in the drag session. Next, appendChild is called to add the new element to the stack, which is the target of the event. Finally, we set some attributes on the new element.

The position of elements in a stack is determined by the left and top attributes. The values of the pageX and pageY properties store the mouse pointer coordinates on the window where the drop occured. This allows us to place the new element at the position where the mouse button was released. This isn't quite the correct way to do this as we actually need to calculate the coordinates of the event relative to the stack. It works here because the board is at the top-left corner of the window.

The label attribute is set to the data from the drag also so that the button has a default label.

This example is fairly simple. One possible change is to use a custom flavour for the data instead of text. The problem with using text is that if the text from an external drag just happens to be set to button, a button will be created on the board. A custom type means that the board will only accept drags from the palette.

The final code is shown below:

<window title="Widget Dragger"
        id="test-window"
        orient="horizontal"
        xmlns="http://www.mozilla.org/keymaster/gat...re.is.only.xul">

  <script src="chrome://global/content/nsDragAndDrop.js"/>
  <script src="chrome://global/content/nsTransferable.js"/>
  <script src="dragboard.js"/>

  <stack id="board"
         style="width:300px; height: 300px; max-width: 300px; max-height: 300px"
         ondragover="nsDragAndDrop.dragOver(event, boardObserver)"
         ondragdrop="nsDragAndDrop.drop(event, boardObserver)">
  </stack>

  <vbox>
    <button label="Button"
            elem="button"
            ondraggesture="nsDragAndDrop.startDrag(event, listObserver)"/>
    <button label="Check Box"
            elem="checkbox"
            ondraggesture="nsDragAndDrop.startDrag(event, listObserver)"/>
    <button label="Text Box"
            elem="textbox"
            ondraggesture="nsDragAndDrop.startDrag(event, listObserver)"/>
  </vbox>
</window>
var listObserver = {
  onDragStart: function (event, transferData, action) {
    var txt = event.target.getAttribute("elem");
    transferData.data = new TransferData();
    transferData.data.addDataForFlavour("text/unicode", txt);
  }
};

var boardObserver = {
  getSupportedFlavours : function () {
    var flavours = new FlavourSet();
    flavours.appendFlavour("text/unicode");
    return flavours;
  },

  onDragOver: function (event, flavour, session) {},

  onDrop: function (event, dropdata, session) {
    if (dropdata.data != "") {
      var elem = document.createElement(dropdata.data);
      event.target.appendChild(elem);
      elem.setAttribute("left", "" + event.pageX);
      elem.setAttribute("top", "" + event.pageY);
      elem.setAttribute("label", dropdata.data);
    }
  }
};

Original Document Information