Many people has asked how to drag and drop nodes on a TreeView. Though this would seem a natural feature on a TreeView, it simply is not that easy to define. First of all, depending on the meaning of the nodes, not all of them can be dragged so that is a first issue, though it could be easily if a tree Node had a 'draggable' property.
Most of the problems lie with dropping. Both the Favorites list on a browser and a window showing the file system of a disk show their information as a tree. In both, only folders can be drop targets, however, files or folders dropped in the file system will be displayed wherever they fit in the preset sort order (usually alphabetically) with folders first. Other trees have other rules. For example, departments and personnel. You can drag and drop people into departments but if you drop a department into another department, you are dropping the people in the dragged department into the target department.
In the end, there are so many possible alternatives that there is no way to easily describe all of them via a simple set of properties. In the end, the only way would be to provide hooks to plug in custom functions to do all the analizing of what can be dropped where and how. However, this doesn't make much sense because, after all, the Drag and Drop utility already provides events to signal when interesting things happen and TreeView provides enough methods to manipulate its nodes so, there would be little to add to these.
This example shows how to match TreeView with Drag and Drop using an arbitrary way of behaving. I'll point out the places where such behaviour can be changed to suit a concrete need. The whole code is contained within this very page so anything not mentioned in the following code boxes can be seen right in this page source.
First of all, I will use a drag proxy to move nodes around, a single proxy for all DD instances so I'll create that first:
DDProxy needs a container, usually a DIV element, which can be empty. My proxy will contain a TreeView instance instead. This is an option, you could simply use an empty outline as DDProxy uses by default, I just do this to show how it can be done. DDProxy asks for the id of the container so I give it one.
I create a DDNode class which is my special variety of DDProxy:
There is nothing new here, I just use the standard way of subclassing within YUI. The commented line is where the functions that handle the behavior of the D&D operations go. First, what to do when dragging starts:
When dragging starts, I first find which tree Node is actually being dragged by using the
getNodeByElement method with the dragged HTML element returned by D&D
Do not confuse Node's
getEl with D&D's method of the same name. Here,
this points to
the DDNode instance. I make invisible the HTML element representing the Node. I use
display:none so that the space it occupies is preserved, you might choose otherwise,
for example, dimming the existing nodes by using a mask or suitable CSS styles.
I said I would drag the branch of the tree in the proxy element. In fact, I will drag a clone of that tree,
I will not move the original node and its children until the element is dropped so, I use
to get all the information for the tree that I might need to make a copy under the
dragTree which I created within my drag proxy,
which I then render.
While the object is being dragged, I check the potential destination. I use
getNodeByElement again to find
out the actual node which is being the potential target. Since
onDragOver will be called many times, I don't
want to repeat the same code over and over so I first check if the current target node is the same as the one already reported,
if it isn't, then I add a className to the new target after removing that same className from the previous one, if there was any,
and make this new target the good one. How you define the className is up to you, I went for a yellow background.
The important matter here is not so much the style but the decisions you can make here.
You have two nodes, the
srcNode which we saved in the
startDrag method and the potential target
tmpTarget. It is up to you and your application whether the source node can be dropped into this target
so, in most cases, you would put some more logic here to decide. Both source and target nodes are actual tree Node instances
(TextNode, HTMLNode or whatever) and you can store extra
custom information in them to help you make that decission.
In the end, you have to either save into
destNode a reference to the node if it is valid, or null if not.
Finally, you get to the drop part. We had the potential target in
destNode or null if it is invalid.
One final check (which could also be done in
onDragOver) we have to make sure not to drop the node
into itself or any of its children so we make sure the source node is not an ancestor of the destination node.
We then pop (remove without destroying it) the source node out of the tree and append it to the destination node.
This is where most of the possible alternatives come into play. I decided to append them as children, your application
will probably require otherwise. TreeView has plenty of methods,
insertAfter to do as required.
Finally I re-render the tree. The changes done by popping and appending the node will not be reflected on the screen until the tree is refreshed. Unfortunatly, this redraws the tree from scratch so all event listeners are removed, including those set by the Drag and Drop utility, so I need to make those nodes draggable again.
Some clean up is in order. Whether the source node is dropped or not, we need to clean some of the changes we
made to the tree, we use the
endDrag method to do that:
We haven't made the tree yet, but that is up to you. Here I build a tree at random and you can see the code in the page source, but it will be irrelevant to whatever you actually do. The important part is how to make the nodes draggable: