«

jQuery UI drag and drop to change parent DIV

Many, many people are using jQuery in web applications. Many are using jQuery to manipulate their UI and more importantly, to add interactivity. Adding the ability to drag and drop elements is actually quite easy using jQuery UI. In this post, I'll be walking you through a basic set up for jQuery UI with drag and drop, but most of our time will be spent discussing how to change the parent div of an element after you've dropped it. This is a problem I recently dealt with, and am glad to report there is a solution! For a more in depth tutorial, please take a look at a post I found on elated.com. Never having used jQuery UI before, it provided me a great foundation for the base of my project!

Importing jQuery libraries

First things first, you have to import the necessary jQuery libraries into the head of your document thusly:

<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.5.0/jquery.min.js"></script>

<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.9/jquery-ui.min.js"></script>

Setting up a draggable object

For any element you want to enable dragging, you need to specify a few parameters. Below is an example of a few of the basic parameters. I'll explain them after you've taken a look.

$(objectToEnableDragging).draggable( {

  containment: '#containmentDIV'
  cursor: 'move',
  revert: true,
  zIndex: 350,

} )

For the object we want to make draggable, we call the jQuery UI .draggable() function and supply the appropriate parameters:

containment: is used to specify an element that will contain the draggable object. In most cases, you'll probably want to contain to the parent div (in fact, there is a predefined value of 'parent' you can supply to the containment parameter). Containment will prevent the draggable element from being dragged outside the specified element. I find it helpful to be very specific when supplying a containment element. If, for instance, you supplied a containment element of .genericDIV, your draggable element would move between all of the genericDIV elements on screen.

cursor: is used to specify the cursor image when dragging this object. In this case, I have chosen the 'move' cursor.

revert: will animate the draggable element back to its starting position if you release the object outside a droppable area.

zIndex: sets the zIndex of the element while dragging. If you want to ensure the draggable element is always on top, it's best to set a value here.

Setting up a droppable area

For any element you want to allow draggable objects to be dropped on, there are additional parameters to set. Below is an example of a few of them. As before, I'll explain them after you've taken a look.

$(objectToEnableDropping).droppable( {

  accept: '.draggableObject',
  drop: handleDrop,
  tolerance: 'touch',

} )

For the object we want to enable dropping, we call the jQuery UI .droppable() function and supply the appropriate parameters:

accept: specifies which draggable objects are allowed to be successfully dropped on top of the droppable element. If this is not specified, any draggable object will be accepted.

drop: this specifies the function to run when a draggable element has been dropped on this particular droppable object. In that function, you can further qualify the draggable object being dropped.

tolerance: specifies the area and position of the draggable object that must be overlapping before the drop will be accepted. In this case, 'touch' covers any overlap of the elements at all.

Now we have basic drag and drop set up! Granted, nothing happens when the draggable object is dropped as we haven't specified any additional actions in our handleDrop function. Let's do that now.

Changing parent div on drop: the issue

Say you have two columns, and you want to enable the dragging of elements between the two columns. Simple! We'll have javascript change the parent div of the draggable element when we drop it on the target in the other column. The following code sets that up:

function handleDrop(event,ui) {

  var targetDIV = document.getElementById('targetDIV');
  var dropTarget = $(this);

  ui.draggable.insertBefore(dropTarget);

};

On lines 3 and 4 we define both the parent div and the droppable target. As this function was called as the result of a drop action, $(this) refers to the droppable target.

On line 6 we insert the draggable element before the droppable target, changing the parent div of the draggable object. The only issue? The draggable element jumps across the screen right after the drop is triggered and the parent div is changed! Why does this happen? To make a long story short, dragging in jQuery modifies the CSS in real time with the new position of the draggable element. When we change the parent div, that same (relative) positioning is kept, making the object appear to jump across the screen. In reality, it's in the same exact position relative to its new parent. After the drop happens and the parent div is reassigned, the revert parameter of the draggable object kicks in and moves it to the correct spot.

Changing parent div on drop: the solution

To both reassign the parent div of your draggable object and make it appear to stay in the same spot, there are a few additional calculations that need to be done. Let's break out the math! We need to modify the handleDrop function as such:

function handleDrop(event,ui) {

  var targetDIV = document.getElementById('targetDIV');
  var dropTarget = $(this);

  //making sure the draggable div doesn't move on its own until we're finished moving it
  ui.draggable.draggable( "option", "revert", false );

  //getting current div old absolute position
  var oldPosition = ui.draggable.offset();

  //assigning div to new parent
  ui.draggable.insertBefore(dropTarget);

  //getting current div new absolute position
  var newPosition = ui.draggable.offset();

  //calculate correct position offset
  var leftOffset = null;
  var topOffset = null;

  if(oldPosition.left > newPosition.left) {
    leftOffset = (oldPosition.left - newPosition.left);
  } else {
    leftOffset = -(newPosition.left - oldPosition.left);
  }

  if(oldPosition.top > newPosition.top) {
    topOffset = (oldPosition.top - newPosition.top);
  } else {
    topOffset = -(newPosition.top - oldPosition.top);
  }

  //instantly offsetting the div to it current position
  ui.draggable.animate( {

    left: '+=' + leftOffset,
    top: '+=' + topOffset,

  }, 0 )

  //allowing the draggable to revert to it's proper position in the new column
  ui.draggable.draggable( "option", "revert", true );    

  };

So what are we doing here? Let's take a look.

On line 7 we disable the revert option for the draggable element. This allows us the peace of mind that the positions we're about to calculate won't be influenced by the element trying to move out from under us.

//making sure the draggable div doesn't move on its own until we're finished moving it
ui.draggable.draggable( "option", "revert", false );

On lines 10, 13 and 16 we store the original position of the draggable element BEFORE we assign the new parent div. After we assign the new parent div (in line 13) we store the new position. This works because we aren't storing the relative position of the draggable element (in jQuery, .position() will return a relative position). Using .offset() will return an absolute screen position for us to use.

//getting current div old absolute position
var oldPosition = ui.draggable.offset();

//assigning div to new parent
ui.draggable.insertBefore(dropTarget);

//getting current div new absolute position
var newPosition = ui.draggable.offset();

Now we know exactly how far the draggable element jumped when the parent div was reassigned, we just need to calculate the offset and move the draggable element accordingly. We also need to compare the original position values to guarantee our offset will be correct no matter which column we're moving from. This happens in lines 18 - 32

//calculate correct position offset
var leftOffset = null;
var topOffset = null;

if(oldPosition.left > newPosition.left) {
  leftOffset = (oldPosition.left - newPosition.left);
} else {
  leftOffset = -(newPosition.left - oldPosition.left);
}

if(oldPosition.top > newPosition.top) {
  topOffset = (oldPosition.top - newPosition.top);
} else {
  topOffset = -(newPosition.top - oldPosition.top);
}

Now, we snap the draggable element to its new position using jQuery animation with a duration of 0. Lines 34 - 40 cover this. It's important to note the position needs to be offset according to the current position of the draggable element, and can't just be set to the offset values.

//instantly offsetting the div to it current position
ui.draggable.animate( {

  left: '+=' + leftOffset,
  top: '+=' + topOffset,

}, 0 )

Finally, we re-enable the revert option and the draggable element animates itself back into the correct position in the new parent div!

//allowing the draggable to revert to it's proper position in the new column
ui.draggable.draggable( "option", "revert", true );

In conclusion

Now you can successfully reassign the parent of a draggable element after dropping it! This allows CSS to manage the position of both the draggable object and its droppable target without relying on an implementation of .sortable(), and without constantly managing the position of all objects that are dragged and dropped onto new columns.

If you come up with other implementations, or other cases where this was useful, I'd love to hear from you!

Share Comment on Twitter