Ignite UI Tree Drag & Drop

[Infragistics] Damyan Petev / Thursday, December 13, 2012

Ignite UI Tree control with Drag and Drop funtionality based on jQuery UI interactionsWith the latest release the Ignite UI Tree has received the nifty feature of drag and drop. It’s a fairly common concept and it allows for a natural (at least I perceive it to be for a long time) interaction with the control’s items. From a user’s stand point it’s all but awesome news  - this would allow for nodes to be moved between one tree’s structure and in our case there’s a bonus of being able to do that between two trees even. The feature provides rich interactions with a familiar visual touch out-of-the box.

From a developer’s view things are also quite pleasing – the jQuery Tree Drag and Drop already handles a decent range of common case scenarios, so you can almost set one property and umm.. profit! Then again, when you do need to set things up it offers a possibly familiar API as you’ll see and has pretty much everything conveniently exposed. And while the widget itself will perform a solid range of drop validations it offers you the option to define your additional custom function.

Quick Setup

As advertised, this can be achieved via a single property and you can then kick back and enjoy the result with the defaults. Drag&Drop is build directly into the tree, so no additional files required and main star is the ‘dragAndDrop’ property:

  1. $('#tree').igTree({
  2.     dragAndDrop: true,
  3.     dataSource: northwindCategories,
  4.     bindings: {
  5.         textKey: 'CategoryName',
  6.         primaryKey: 'CategoryID',
  7.         valueKey: 'CategoryID',
  8.         childDataProperty: 'Products',
  9.         bindings: {
  10.             textKey: 'ProductName',
  11.             valueKey: 'ProductID',
  12.             primaryKey: 'ProductID'
  13.         }
  14.     }
  15. });

This setup will pretty much produce a fully functional tree with the bare minimum of setup and the equivalent ASP.NET MVC helper definition is about as simple:

  1. @(Html.Infragistics().Tree(Model).ID("tree")
  2.     .DragAndDrop(true)
  3.     .Bindings(binding =>
  4.     {
  5.         binding.PrimaryKey("CategoryID")
  6.             .TextKey("CategoryName")
  7.             .ValueKey("CategoryID")
  8.             .ChildDataProperty("Products")
  9.             .Bindings(b1 =>
  10.             {
  11.                 b1.TextKey("ProductName")
  12.                 .ValueKey("ProductID")
  13.                 .PrimaryKey("ProductID");
  14.             });
  15.     }).DataBind().Render())

The resulting tree is the one you see in the heading image. Yes, the helpful indicator to the side too! Make sure you stop by the documentation and check out the Enabling Drag-and-Drop section where you can discover that there’s an additional ‘allowDrop’ setting that allows for nodes to be dropped in you igTree from another igTree!

Customizations

Now we come to the part that may be familiar to some of you – the Ignite UI Tree also accepts a ‘Drag and Drop Settings’ object with various settings for the behavior of the feature. Now a small deviation  - the reason why the settings might be not so hard to understand is because the control internally implements interactions from jQuery UI – the Draggabe (“Allow elements to be moved using the mouse.”) and Droppable (“Create targets for draggable elements.”) widgets. Taking a look at the descriptions for those you can tell why – just like the concepts of Drag and drop go together, so do those widgets and the tree  initializes both itself. Oh and that settings object? Yeah, some settings go almost directly to the Draggabe Widget’s options:

  • helper – Defines what element (helper) should be used to for dragging display. More on that in the future, lets say you can use the original node or a clone too, just don’t deep clone :) Plus the helper I think will almost always end up being the better UX.
  • dragOpacity( equivalent to ‘opacity’) – Opacity for the helper while being dragged.
  • zIndex – This directly corresponds to the CSS z-index Property that will be applied to the dragged element. It specifies the stacking order of elements – making the ones with higher index come on top of those with lower. The default 10 is usually enough, but if for some reason you are having issues with your dragged helper being overlapped by something this is the property to increase.
  • revert – this one is mildly different as for the Tree it’s a Boolean while the jQuery UI Draggable takes both boolean and two string options. The reasoning behind this is that in the context of drag and drop within the jQuery tree not all of those make sense – like reverting on ‘valid’ drops or always reverting. Plus it makes the option much simpler – false stays the same, while true defaults to ‘invalid’ instead.
  • revertDuration - The duration of the revert animation, in milliseconds. Ignored if the revert option is false.
  • dragStartDelay ( equivalent to ‘delay’) - Time in milliseconds after ‘mousedown’ until dragging should start. This option can be used to prevent unwanted drags when clicking on an element. This one is big on user experience I think – trust me when I say you don’t want this too low with complex control with elements that perform additional functions. Based on that the minimum you want is at least 100 milliseconds or you risk drags starting on simple selection and node expansion. It’s pointless overhead and even potentially successful but unintended buy the user moving of nodes.
  • containment - Constrains dragging to within the bounds of the specified element or region. This one is quite versatile – taking a range of values like a selector, the actual element to contain within, string enumeration or even an array of values defining a bounding box. The default false setting restricts the drag to the parent element of the tree.

Possibly half of the others are left to the default and some the Tree widget reserves for itself. There are of course ways to ‘hack’ into those, but let’s not do that.. at least not just yet. But hey, here’s one that barely counts as one – the cursorAt that defines where the helper markup should appear relative to the mouse cursor regardless of what the name may imply is also defined, however it’s value is set form a constant within the tree and that can be replaced. Keep in mind there’s probably a good reason for that not be exposed, so use with caution – there’s at least one danger  - if you go too far and bring the helper under the mouse (which you might decide to do) I guess the validation of the drop target fails and all you’ll be seeing is invalid visual – you need the mouse at least 1px clear from the helper sadly. But hey, you can at least reposition the helper closer and to the bottom right corner (Explorer style) for example:

Ignite UI Tree control with Drag and Drop helper position and markup manipulation

I’ll admit I didn’t do a particularly good job with that, but hey it’s good to know you can tweak that. If you have been playing with the feature so far you’ll also notice the invalid message is different (it usually  shows the name of the node you are dragging) – that’s because you can provide markup for any of the multiple helper states. You can look those up in the Drag-and-Drop Property API Reference where you’ll see the defaults too. Oh right, the code:

  1. $.ig.loader('igTree', function () {
  2.     $.extend($.ui.igTree.prototype, {
  3.         _const: {
  4.             dragCursorAt: {
  5.                 top: -20,
  6.                 left: 0
  7.             }
  8.         }
  9.     });
  10.     $('#tree').igTree({
  11.         dragAndDrop: true,
  12.         dragAndDropSettings: {
  13.             dragAndDropMode: 'default',
  14.             dragStartDelay: 100,
  15.             revert: true,
  16.             invalidMoveToMarkup: '<div><p><span></span> <strong> Invalid location. </strong></p></div>',
  17.             containment: "#tree"
  18.         },
  19.         dataSource: northwindCategories,
  20.         bindings: {
  21.             textKey: 'CategoryName',
  22.             primaryKey: 'CategoryID',
  23.             valueKey: 'CategoryID',
  24.             childDataProperty: 'Products',
  25.             bindings: {
  26.                 textKey: 'ProductName',
  27.                 valueKey: 'ProductID',
  28.                 primaryKey: 'ProductID'
  29.             }
  30.         }
  31.     });
  32. });

Regarding the various markups – you’ll notice they all contain an empty span. What’s up with that? Well, the span is the one being targeted by the widget to apply classes for the icons. If you want the icon it’s good to include a span in your markup. You can remove it to remove the icon, or leave just the span (with unbreakable space in the paragraph for height) to have a helper with icon only.

Notable mentions from the snippet above also include the mode of the feature ( move and copy available, the default option combines both with a ‘Ctrl’ as modifier. The line at 17 (containment: "#tree") prevents the dragging to go on beyond the boundaries of the tree itself (as in my case with a very simple layout the tree’s parent is the body and that doesn’t always produce good results).

Enhanced Events

The Tree will fire a number of events during the drag and drop interaction. The familiar equivalents of jQuery UI Draggable (start –> drag –> stop) are as follows: ‘dragStart’ –> ‘drag’ –> ‘dragStop’. A slight name change really to better suit the context of a more complex widget. Speaking of which, the control as you can imagine internally handles the original Draggable events to do its work and conveniently fires event for you to handle in the meantime, but that’s not the whole story. While, yes, the Tree will pass you the original Event object, the arguments you get alongside will be extended with a bunch of useful properties containing the path of the node in question, the actual element itself, its data record and appropriate binding. Again you can refer to the API reference or Documentation to get additional info on those. All are cancelable.

Since the Ignite UI Tree also must be the target of the drop, it also has an initialized Droppable widget. However, the events for that are different – the tree will fire events as the node is being dropped and after it has been dropped. As you can imagine the first one can be canceled and is a good place for last checks to see if the node dropping is in an acceptable position. These come from the single ‘drop’ event of the jQuery UI widget, so what happens with the rest? Well, they didn’t make much sense for the Tree, but if you really need to handle some of them for any reason don’t despair – unlike the draggable deeper inside the structure of the control, the Droppable is initialized of the very same element as the igTree and you can handle it’s events just as described in it’s API, for example:

  1. $("#tree").on("dropactivate", function (event, ui) {
  2.     alert("We have take off!")
  3. });

Also you can handle the ‘drop’ as well, but keep in mind you won’t get the extra info the Tree provides in the delegated events and when you do handle drop your handler is likely to be triggered before the Tree’s so you can’t really verify the job is being done there. Just, whatever you do, don’t handle those with alert like I did! :)

Limitations and not so much

I mentioned the control will do a plentiful amount of validation (even without a custom function) in order to maintain reasonable behavior (like the droppable not accepting anything you throw at it an such). The tree will also validate the drop target is an acceptable part of an igTree widget’s UI. in addition to that, as I mentioned before, there is also a binding validation. What this means is that the node being dropped on must have bindings compatible with the dragged node. Take the case above – we have a top tier of Categories whose children are products. Based on that the control will not allow you to drop one Category into another (as the accepted child is defined as Product already). Same goes with trying to drop product in the root (where category is expected). If your models allows identical bindings on all levels then you can ignore that.

This leads to some issues with performing Drag & Drop between two trees as well – you can’t simple define two completely unrelated trees and expect to be able to move nodes between them. Again similar bindings should exist in the trees in order for the move or copy to be accepted. I’m not sure if it’s a limitation if it’s reasonable, but hey you can always work some magic to get around it.

Resources

First let me again remind of the elaborate Ignite UI Tree Documentation and jQuery API Reference. Then don’t forget the awesome samples:

  • Drag and Drop within a single Tree – you can observe here two interesting behaviors of the control – when checkboxes are enables their state will not be preserved when moving, however when dropped on a parent whose tri-state box is fully checked (meaning every child also is) then the newly added node will follow that rule and also render with ticked checkbox. Also dragging a node over unexpanded one will (after a short timeout) expand the drop target.

  • Drag and Drop with two Trees –  moving nodes between controls with identical binding while avoiding dropping nodes on the ones marked as a file via a custom validation function.
  • Simple File Manager – Using Drag & Drop to organize files and folders and an additional droppable target you can drop nodes to delete them – all while watching the respective events fly in the log.

Stay tuned!  There are some more neat tricks to share about this feature and I plan on another part along with demos!

Summary

We can all agree that Drag and Drop is awesome, simple and intuitive and it’s always great to be able to provide that for your users. As we’ve seen that is as easy as child's play with a mere property or two. However, once you know what you want and the defaults are not it – there’s a plethora of knobs to tweak and some of them.. do they sound familiar? Yes they do, as some come directly from the jQuery UI Dragable and Droppable widgets. Helpers are separated for cases so each can be controlled separately. The control handles much of the work for you by validating proper drop targets on multiple criteria while still providing you with the option to build on that – be it with validation function or by hooking up to one of the elaborate events. Go check out those samples and stay tuned for more!

Donwload your Ignite UI Free Trial now!

And as always, you can follow us on Twitter @DamyanPetev and @Infragistics and stay in touch on Facebook, Google+ and LinkedIn!