Skip to content
Martin Wendt edited this page May 18, 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.
    // 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: {
    preventVoidMoves: true, // Prevent dropping nodes 'before self', etc.
    preventRecursiveMoves: true, // Prevent dropping nodes on own descendants
    autoExpandMS: 400,
    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);
    }
    draggable: {
      zIndex: 1000,
      scroll: false
    }
  }
});

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] Allow Scrolling inside the tree container while dragging

The Fancytree dnd extension enables the standard jQuery Draggable options for auto scrolling:

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

but depending on the the surrounding markup, it may also be necessary to add some CSS to give the Fancytree container 'position: relative':

#tree .fancytree-container {
    position: relative;
}

[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);
  }
});
Clone this wiki locally