Skip to content
Martin edited this page Aug 25, 2014 · 38 revisions

About Fancytree drag-and-drop extension.

Add Drag-and-Drop support:

Example

In addition to jQuery, jQuery UI, and Fancytree, include jquery.fancytree.dnd.js:

  <script src="//ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js" type="text/javascript"></script>
  <script src="//ajax.googleapis.com/ajax/libs/jqueryui/1/jquery-ui.min.js" type="text/javascript"></script>
  <link href="skin-win8/ui.fancytree.css" rel="stylesheet" type="text/css">
  <script src="js/jquery.fancytree.js" type="text/javascript"></script>
  <script src="js/jquery.fancytree.dnd.js" type="text/javascript"></script>

Enable dnd extension and pass options:

$("#tree").fancytree({
  extensions: ["dnd"],
  dnd: {
    // Available options with their default:
    autoExpandMS: 1000, // Expand nodes after n milliseconds of hovering.
    draggable: null,    // Additional options passed to jQuery draggable
    droppable: null,    // Additional options passed to jQuery droppable
    preventRecursiveMoves: true, // Prevent dropping nodes on own descendants
    preventVoidMoves: true,      // Prevent dropping nodes 'before self', etc.
    focusOnClick: false,  // Focus, although draggable cancels mousedown event (#270)
    // Events that make tree nodes draggable
    dragStart: null,    // Callback(sourceNode, data), return true, to enable dnd
    dragStop: null,     // Callback(sourceNode, data)
    // Events that make tree nodes accept draggables
    dragEnter: null,    // Callback(targetNode, data)
    dragOver: null,     // Callback(targetNode, data)
    dragDrop: null,     // Callback(targetNode, data)
    dragLeave: null     // Callback(targetNode, data)
  },
  [...]
});

All API function are passed a data object:

{
    node: ...,
    tree: ...,
    options: ...,
    originalEvent: ...,
    otherNode: ..., 
    ui: ..., 
    hitMode: ..., 
    draggable: ...,
}

Example

$("#tree").fancytree({
  extensions: ["dnd"],
  
  // .. other options...
  
  dnd: {
    autoExpandMS: 400,
    draggable: { // modify default jQuery draggable options
      zIndex: 1000,
      scroll: false,
      revert: "invalid"
    },
    preventVoidMoves: true, // Prevent dropping nodes 'before self', etc.
    preventRecursiveMoves: true, // Prevent dropping nodes on own descendants
    dragStart: function(node, data) {
      // This function MUST be defined to enable dragging for the tree.
      // Return false to cancel dragging of node.
//    if( data.originalEvent.shiftKey ) ...          
      return true;
    },
    dragEnter: function(node, data) {
      /* data.otherNode may be null for non-fancytree droppables.
       * Return false to disallow dropping on node. In this case
       * dragOver and dragLeave are not called.
       * Return 'over', 'before, or 'after' to force a hitMode.
       * Return ['before', 'after'] to restrict available hitModes.
       * Any other return value will calc the hitMode from the cursor position.
       */
      // Prevent dropping a parent below another parent (only sort
      // nodes under the same parent):
//    if(node.parent !== data.otherNode.parent){
//      return false;
//    }
      // Don't allow dropping *over* a node (would create a child). Just
      // allow changing the order:
//    return ["before", "after"];
      // Accept everything:
      return true;
    },
    dragOver: function(node, data) {
    },
    dragLeave: function(node, data) {
    },
    dragStop: function(node, data) {
    },
    dragDrop: function(node, data) {
      // This function MUST be defined to enable dropping of items on the tree.
      // hitMode is 'before', 'after', or 'over'.
      // We could for example move the source to the new target:
      data.otherNode.moveTo(node, data.hitMode);
    }
  }
});

Recipes

[Howto] Copy a node on drop

When a node is copied from a the same tree, we should generate a new key, or let the tree generate one.

  dragDrop: function(node, data) {
    newNode = data.otherNode.copyTo(node, data.hitMode, function(n){
      n.title = "Copy of " + n.title;
      n.key = null; // make sure, a new key is generated
    });
  }

[Howto] Control scrolling inside the tree container while dragging

By default, jQuery Draggable options for auto scrolling are enabled for the tree:

scroll: true,
scrollSpeed: 7,
scrollSensitivity: 10,

Scrolling also requires that the Fancytree container has 'position: relative'. Starting with v2.1 this is added to common CSS:

ul.fancytree-container {
    position: relative;
}

While this may be handy when nodes should be dragged inside one large tree, scrolling may be undesirable when nodes are dragged to outside targets.
In order to prevent scrolling inside the tree container, this can be turned off for draggable and container:

$("#tree").fancytree({
  ...  
  dnd: {
    ...
    draggable: { // modify default jQuery draggable options
      scroll: false
    },

and custom CSS

ul.fancytree-container {
    position: inherit;
}

[Howto] Drop on a lazy node

Dropping a node onto a lazy folder may not work as expected: The item that is dragged will appear in that folder but it stops the node from performing the ajax request.
This is 'works as designed': lazy folders only generate an ajax reques, if the children property is null or undefined in order to prevent lazy-loading a second time.

We could however expand the node before adding the dropped node:

dragDrop: function(node, data) {
  node.setExpanded(true).always(function(){
    // Wait until expand finished, then add the additional child
    data.otherNode.moveTo(node, data.hitMode);
  });
}

(Another pattern could be: issue an ajax request to notify the server about the new node. Then reload the branch.)

[Howto] Accept standard jQuery UI draggables as drop source

<p class="draggable">
  Draggable.
</p>

Connect the draggable to the tree:

$(".draggable").draggable({
  revert: true, //"invalid",
  cursorAt: { top: -5, left: -5 },
  connectToFancytree: true,   // let Fancytree accept drag events
});

and handle drop events:

$("#tree").fancytree({
  extensions: ["dnd"],
  ...
  dnd: {
    ...
    dragEnter: function(node, data) {
       return true;
    },
    dragDrop: function(node, data) {
      if( !data.otherNode ){
        // It's a non-tree draggable
        alert("dropped " + $(data.draggable.element).text());
        return;
      }
      data.otherNode.moveTo(node, data.hitMode);
    }
  }
});

[Howto] Accept Fancytree nodes as source for a standard droppable

Assuming we have a stabdard droppable

<p class="droppable">
  Droppable.
</p>

and the tree has the dnd extension enabled:

$("#tree").fancytree({
  extensions: ["dnd"],
  ...
  dnd: {
    ...
    dragStart: function(node, data) {
      return true;
    },
    ...
  }
});

Nodes can be dropped to the standard droppables, and we can access the original source node like so::

$(".droppable").droppable({
  drop: function(event, ui){
    var sourceNode = $(ui.helper).data("ftSourceNode");
    alert("Dropped source node " + sourceNode);
  }
});
[Howto] Prevent ext-dnd to suppress mouse clicks

Fancytree uses tthe standard jQuery UI draggable plugin to implement drag'n'drop. `draggable`` however prevents mouse clicks from setting the focus (so you can drag an object without activating it).

In combination with keyboard navigation, this can prevent setting the focus to the tree container, so that keyboard input is not delivered to the tree.
Use the dnd.focusOnClick: true option in this case:

$("#tree").fancytree({
  dnd: {
    focusOnClick: false,  // Focus, although draggable cancels mousedown event (#270)
    ...
  }
  [...]
});
Clone this wiki locally