how to revert position of a jquery UI draggable based on condition

$("#droppable").droppable({ 
    accept: function(dropElem) {
        //dropElem was the dropped element, return true or false to accept/refuse it
        if($(this).data('stoneclass') == dropElem.attr("id")){
            return true;
    }
}
});​

This was used to accept only one draggable, from a group of draggables ; to one correct droppable, in a group of droppables.

P.S sorry for the language if it is not understandable =)


Try adding that code into the "drop" event. Here is a demo.

HTML

<div class="draggable ui-widget-content correct"><p>1</p></div>
<div class="draggable ui-widget-content"><p>2</p></div>
<div class="draggable ui-widget-content"><p>3</p></div>
<div class="draggable ui-widget-content"><p>4</p></div>
<br style="clear: both">
<div id="droppable" class="ui-widget-header">
  <p>Drop me here</p>
</div>

Script

$(function() {

 $(".draggable").draggable({ revert: true });

 $("#droppable").droppable({
  activeClass: 'ui-state-hover',
  hoverClass: 'ui-state-active',
  // uncomment the line below to only accept the .correct class..
  // Note: the "drop" function will not be called if not ".correct"
  //  accept : '.correct',
  drop: function(event, ui) {
   // alternative method:
   // if (ui.draggable.find('p').text() == "1") {
   if (ui.draggable.is('.correct')) {
    $(this).addClass('ui-state-highlight').find('p').html('You got it!');
    // cloning and appending prevents the revert animation from still occurring
    ui.draggable.clone(true).css('position', 'inherit').appendTo($(this));
    ui.draggable.remove();
   } else {
    $('#droppable > p').html('Not that one!')
    setTimeout(function(){ $('#droppable > p').html('Drop here'); }, 1000);
   }
  }
 });

});

As you can see in the script, you can either look for the .correct class or the text inside (commented out line)


I had a use case where I had to run some logic in the droppable's drop method to determine if the draggable should revert. I used the following to achieve this:

$('.draggable-elements').draggable({
  revert: function() {
    if ($(this).hasClass('drag-revert')) {
      $(this).removeClass('drag-revert');
      return true;
    }
  }
});

$('.droppable-elements').droppable({
  drop: function(event, ui) {
    if ( % conditional logic check here % ) {
      return $(ui.draggable).addClass('drag-revert');
    }
  }
});

My use case is a table where each cell is a droppable representing a 5 minute period. My draggables are bookings which start in one cell, but run over many cells in a row to represent the booking time.

I did not want any draggables to overlap, as that would represent a double booking. So, after the drop I find all draggables in the row, excluding the one I just moved, and check for any overlaps. If there is an overlap, my conditional logic returns true and I revert the draggable.

Next stage is to fire an ajax call to update the database, if server-side logic says the move is invalid I hope to return an error status and revert the draggable.

P.S. this is my first answer, sorry if I've done anything wrong. Tips on how to provide good answers gladly accepted.

Edit: After trying my AJAX call, I realised there is a window of opportunity for the droppable.drop function to do it's thing before the draggable.revert function runs. By the time my AJAX call returned false, the window had passed and the class was being added too late for draggable.revert to react.

As such, I'm no longer relying on the draggable.revert function, instead acting solely on the AJAX response and if false, using animate() to return the draggable to it's original position, as follows:

$( '.draggable-elements' ).draggable();

$( '.droppable-elements' ).droppable({
  drop: function( event, ui ) {
    var postData = {
      id: 1,
      ...
    };
    $.post( '/ajax/url', postData, function( response ) {
      var response = $.parseJSON( response );
      if ( response.status != 1 ) {
        return ui.draggable.animate( {left: 0, top: 0}, 500 );
      }
    }
  }
});

You may need to have draggable.start/drag add the original left and top values of the draggable as data attributes, but for me the above worked fine.


There are some built-in options for this, on your .draggable(), set the revert option to 'invalid', and it'll go back if it wasn't successfully dropped onto a droppable, like this:

$("#draggable").draggable({ revert: 'invalid' });

Then in your .droppable() set what's valid for a drop using the accept option, for example:

$("#droppable").droppable({ accept: '#draggable' });​

Anything not matching this selector gets reset when you let go, you can see a full demo here. The accept option also takes a function if you need filtering a selector can't provide, like this:

$("#droppable").droppable({ 
  accept: function(dropElem) {
    //dropElem was the dropped element, return true or false to accept/refuse it
  }
});​