-
-
Notifications
You must be signed in to change notification settings - Fork 602
ExtDnd5
About Fancytree drag-and-drop extension (HTML5 based).
- Status: experimental
- example
Add Drag-and-Drop support:
- Compatible with the HTML Drag and Drop API
- Drag nodes inside one tree, i.e. re-order, move, or copy.
- Drag nodes between different trees (even on different iframes, windows or pages).
- Any element that has the
draggable="true"
attribute set may be dropped over a Fancytree node. - Drop a Fancytree node on any element that implements the HTML Drag and Drop API
Note: There is also another extension available that adds drag-and-drop support using the jQuery UI draggable and jQuery UI droppable API.
In addition to jQuery, jQuery UI, and Fancytree, include jquery.fancytree.dnd5.js
:
<script src="//code.jquery.com/jquery-1.12.1.min.js"></script>
<script src="//code.jquery.com/ui/1.11.4/jquery-ui.min.js"></script>
<link href="skin-win8/ui.fancytree.css" rel="stylesheet">
<script src="js/jquery.fancytree.js"></script>
<script src="js/jquery.fancytree.dnd5.js"></script>
Enable dnd
extension and pass options:
$("#tree").fancytree({
extensions: ["dnd5"],
dnd5: {
// Available options with their default:
autoExpandMS: 1500, // Expand nodes after n milliseconds of hovering.
preventForeignNodes: false, // Prevent dropping nodes from different Fancytrees
preventNonNodes: false, // Prevent dropping items other than Fancytree nodes
preventRecursiveMoves: true, // Prevent dropping nodes on own descendants
preventVoidMoves: true, // Prevent dropping nodes 'before self', etc.
scroll: true, // Enable auto-scrolling while dragging
scrollSensitivity: 20, // Active top/bottom margin in pixel
scrollSpeed: 20, // Pixel per event
// Events (drag support)
dragStart: null, // Callback(sourceNode, data), return true, to enable dnd drag
dragDrag: $.noop, // Callback(sourceNode, data)
dragEnd: $.noop, // Callback(sourceNode, data)
// Events (drop support)
dragEnter: null, // Callback(targetNode, data), return true, to enable dnd drop
dragOver: $.noop, // Callback(targetNode, data)
dragExpand: $.noop, // Callback(targetNode, data), return false to prevent autoExpand
dragDrop: $.noop, // Callback(targetNode, data)
dragLeave: $.noop // Callback(targetNode, data)
},
[...]
});
All callback methods are passed a data
object:
{
node: ...,
tree: ...,
options: ...,
originalEvent: ...,
otherNode: ...,
hitMode: ..., // 'over', 'after', 'before'
dataTransfer: dataTransfer, // access drag data, drag iamge, and drop-effect
dropEffect: (string), // set by dragend and drop ('move', 'copy', or 'link')
isCancelled: (boolean), // set by dragend and drop
}
$("#tree").fancytree({
extensions: ["dnd5"],
// .. other options...
dnd5: {
autoExpandMS: 1500,
preventRecursiveMoves: true, // Prevent dropping nodes on own descendants
preventVoidMoves: true, // Prevent dropping nodes 'before self', etc.
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 ) ...
// if( node.isFolder() ) { return false; }
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.
*/
// Example:
// Prevent dropping a parent below another parent (only sort
// nodes under the same parent):
// if(node.parent !== data.otherNode.parent){
// return false;
// }
// Example:
// Don't allow dropping *over* a node (which would create a child). Just
// allow changing the order:
// return ["before", "after"];
// Accept everything:
return true;
},
dragExpand: function(node, data) {
// return false to prevent auto-expanding data.node on hover
},
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.
// data.hitMode is 'before', 'after', or 'over'.
// We could for example move the source to the new target:
data.otherNode.moveTo(node, data.hitMode);
}
}
});
This is accomplished by the node.copyTo()
method.
When a node is copied from the same tree, we should define 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
});
}
The auto scroll feature is on by default. It works by scrolling the whole tree when the drag cursor is near the upper or lower margin of the scroll parent:
scroll: true,
scrollSpeed: 7,
scrollSensitivity: 10,
The container may also be set to a fixed sized by a custom rule, to enable scrolling inside the tree-container only:
ul.fancytree-container {
height: 200px;
overflow-y: auto;
}
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 request 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.)
We can drop any element onto Fancytree, that satisfies the HTML Drag and Drop API, for example:
<span class="drag-source" draggable="true"
ondragstart="event.dataTransfer.setData('text/plain', 'Drag me');">
Drag me</span>
Also other elements may generate drop-events by default:
-
<input>
elements and<a>
tags - selected text from a text editor
- Files from your computer's desktop, ...
$("#tree").fancytree({
extensions: ["dnd5"],
...
dnd5: {
preventNonNodes: false, // Allow dropping items other than Fancytree nodes
...
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);
/* This function MUST be defined to enable dropping of items on
* the tree.
*/
var transfer = data.dataTransfer;
node.debug("drop", data);
if( data.otherNode ) {
// Drop another Fancytree node from same frame
// (maybe from another tree however)
var sameTree = (data.otherNode.tree === data.tree);
data.otherNode.moveTo(node, data.hitMode);
} else if( data.otherNodeData ) {
// Drop Fancytree node from different frame
node.addChild(data.otherNodeData, data.hitMode);
} else {
// Drop a non-node
transfer.effect = "copy";
node.addNode({
title: transfer.getData("text/plain")
}, data.hitMode);
}
node.setExpanded();
}
}
}
});
Note: ext-dnd5
does not make use of the jQuery UI draggable
API.
We can drop a Fancytree node on any target, that implements the HTML Drag and Drop API.
For example these elements are potential drop targets:
- Another Fancytree widget on the same page or another browser window
-
<input>
and<textarea>
elements - Text editor windows
- File Explorer, your computer's desktop, ...
Note: ext-dnd5
does not make use of the jQuery UI droppable
API.
Notes: See also [Howto] Control scrolling inside the tree container while dragging
$("#tree").fancytree({
dnd5: {
...
draggable: {
revert: "invalid"
scroll: false,
appendTo: "body", // Helper parent (defaults to tree.$container)
helper: function(event) {
var $helper,
sourceNode = $.ui.fancytree.getNode(event.target),
$nodeTag = $(sourceNode.span);
$helper = $("<div class='fancytree-drag-helper'><span class='fancytree-drag-helper-img' /></div>")
.append($nodeTag.find("span.fancytree-title").clone());
// Attach node reference to helper object
$helper.data("ftSourceNode", sourceNode);
// we return an unconnected element, so `draggable` will add this
// to the parent specified as `appendTo` option
return $helper;
},
},
}
[...]
});
There is no built-in 'useModifiers' option, because the potential use cases are too diverse. But we can implement the desired behavior using callbacks.
There is no built-in 'multiDnd' option, because the potential use cases are too diverse. But we can implement the desired behavior using callbacks.
Documentation Home - Project Page - Copyright (c) 2008-2022, Martin Wendt (https://wwWendt.de)