Tutorial for TreeVirtual MDragAndDropSupport mixin
qx.ui.treevirtual.TreeVirtual is a great and versatile widget, but it doesn’t support Drag & Drop out of the box. This is where the MDragAndDropSupport mixin comes in. This mixin
- maintains the focus indicator during a drag session
- provides a insertion cursor for reordering nodes
- checks which node types can be dropped on which node types
- automagically provides event handlers implementing the most common drag & drop behaviors
- fires a “draghover” if the drag cursor hovers a node for a specific time, allowing to open nodes, dynamically retrieve node children etc., during a drag session.
- allows to sort children according to sophisticated rules
- provides methods to move or copy nodes
Let’s look at an example. First, in your code, you need to include the mixin and a related mixin:
qx.Class.patch(qx.ui.treevirtual.TreeVirtual, qx.ui.treevirtual.MDragAndDropSupport); qx.Class.include(qx.ui.treevirtual.TreeVirtual, qx.ui.treevirtual.MNode);
You need to use “patch” instead of “include” here, because the MDragAndDropSupport mixin needs to override the “supportsDrop” method (qx.Class.include refuses to override methods).
var tree1 = new qx.ui.treevirtual.TreeVirtual(["Tree 1"]); var dataModel1 = tree1.getDataModel(); // ... set some properties and add to document, skipped here ... // var inbox = dataModel1.addBranch(null, "Inbox", true); tree1.setNodeType(inbox,"Folder"); for (var i = 1; i < 5; i++) { var m = dataModel1.addLeaf(inbox, "Message #" + i); tree1.setNodeType(m,"Message"); }
Here, we are creating an inbox folder and add some messages to it. Note the “setNodeType” method, which assigns a type to the node, which can be any string. This is different from the “type” property of the data node (which can only be either qx.ui.treevirtual.SimpleTreeDataModel.Type.LEAF or qx.ui.treevirtual.SimpleTreeDataModel.Type.BRANCH) and is internally stored as the data.MDragAndDropSupport.type property. It is important to set the type for all nodes, because much of the magic of the mixin relies on this property being set.
var noSpamFolder = dataModel1.addBranch(null, "No Spam here", true); tree1.setNodeType(noSpamFolder, "NoSpamFolder"); var spamFolder = dataModel1.addBranch(null, "Spam", true); tree1.setNodeType(spamFolder, "Folder"); for (var i = 1; i < 10; i++) { var spam = dataModel1.addLeaf(spamFolder, "Spam Message #" + i); tree1.setNodeType(spam,"Spam"); } // render nodes dataModel1.setData();
What this piece of code does is to create two folders, “No Spam here” and “Spam”, with types “NoSpamFolder” and “Folder”, respectively. We then add some Spam (type “Spam”) to the spam folder. Finally, we update the tree.
Now comes the drag and drop configuration
tree1.setAllowDragTypes(["*"]); tree1.setAllowDropBetweenNodes(true); tree1.setAllowDropTypes([ ['Spam','Folder'], ['Message','Folder'],['Folder','Folder'],['Message','NoSpamFolder'] ]);
The first line tells the mixin that the user can drag any type of node. Alternatively, you could also provide an array of types, i.e. allow to drag some but not others.
Then, we tell the mixin that a drop can occur “between” nodes, i.e., the focus indicator turns into a line if the cursor is placed between two tree nodes. This allows the reordering of nodes.
The third command specifies the dropping policy. In our case, Spam and Messages can be dropped on Folders, Folders on Folders, and Messages on NoSpamFolder type nodes. This effectively rules out that “Spam” nodes can be dropped on “NoSpamFolder” nodes.
We need to tell the mixin what to do when the user drops a node on a different node or between nodes. The standard behavior is to move the node from its original place to the new position. This is very easy to implement:
tree1.addEventListener("dragdrop",function(event){ // move node to new place this.moveNode(this.getDropData(event)); },tree1);
You could, however, add a more sophistated behavior based on the data supplied by the getDropData function.
The dragdrop event handler is usually the only one that you specifically need to provide - all others are wired with a default behavior that is suitable for most needs. You can, however, setup your own event handlers by adding event listeners for the “dragstart”, “dragover”, “dragout”, “dragdrop” and “dragend” events at this point.
There is one additional event, “draghover”, fired when the drag cursor hovers over a node for a specific time (which can be set in the dragHoverTimeout property and defaults to 1000 Milliseconds). This allows the developer to easily implement behavior such as automatically open nodes, retrieving child nodes etc. during the drag session.
// auto-select node on hover timout tree1.addEventListener("draghover",function(event){ var node=event.getData(); var row=node.row; this.getSelectionModel().setSelectionInterval(row,row); },tree1);
In this case, we auto-select the node, which fires the “selectionChange” event.
Finally, we need to turn Drag & Drop on:
tree1.setEnableDragDrop(true);
This must be the last mixin property that is set, because it initializes Drag & Drop based on the properties that have been set earlier.
We now create a second tree to and from which node data can be dragged.
var tree2 = new qx.ui.treevirtual.TreeVirtual(["Tree 2"]); var dataModel2 = tree2.getDataModel(); // ... skip initialization code ... // var dropNode = dataModel2.addBranch(null, "Drop here", true); tree2.setNodeType(dropNode,"Folder"); var dropNode2 = dataModel2.addBranch(dropNode, "Or here", true); tree2.setNodeType(dropNode2,"Folder"); for(var i=1; i<11; i++) { var f = dataModel2.addBranch(dropNode, "Some Folder #" + i, true); tree2.setNodeType(f,"Folder"); } // render dataModel2.setData(); /**** drag & drop support ****/ tree2.setAllowDragTypes(['Message','Folder']); tree2.setAllowDropTypes([['*','Folder']]);
So far, so good. The difference is only that we only allow “Message” and “Folder” type nodes to be dragged, “trapping” dragged “Spam” and “NoSpamFolder” items in the second tree. We also specify that everything (”*”) can be dragged on a Folder (This can be also be the other way round, like so: [’MyMagicType’,’*’]).
Now, we configure node sorting:
tree2.setSortAfterDrop(true); tree2.setSortChildNodesBy({ 'dragType' : ['Folder','Message','Spam'], 'label' : "asc" });
This turns auto-sorting of nodes on and sets the sorting policy. This policy is laid out in a hash map, the key being the node property, and the value the sorting policy.
The ‘dragType’ hash map key is a shorthand alias for the ‘data.MDragAndDropSupport.type’ property. You could also sort by ‘type’ only, which would sort by LEAF and BRANCH.
If the value of a key-value pair is a string, it will either be “asc”, which sorts ascending, or “desc”, which sorts descending. If the value is an array, it evaluates the value of the selected node property against the order of elements in this array - a value that occurs earlier in the array takes precedence over a value that occurs later.
This leaves only to implement the drapdrop event handler and we’re done!
tree2.addEventListener("dragdrop",function(event){ this.moveNode(this.getDropData(event)); },tree2); tree2.setEnableDragDrop(true);
You can see this example in action in the “TreeVirtual 6” Page in the Examples section of the qooxdoo demo browser. Enjoy!
