11

I'm making an app, that is using one droppable div and a few draggable divs. How can I make the droppable to not accept more than one draggable div? I Googled, but didn't find any workaround.


A workaround came up in mi mind. How can i check is there's dropped element in this droppable div? If it's busy then revert this draggable, which is trying to be dropped

Gumbo
  • 643,351
  • 109
  • 780
  • 844
Emil Avramov
  • 881
  • 5
  • 21
  • 38

9 Answers9

29

OK found a nice solution for this, essentially on 'drop' I set the droppable to only accept the item which has been dragged into it.

When you 'disable', the 'out' event that you need to re-initialize isn't available anymore, so instead I just switched the eligible items around.

Then it's possible for me to use the OUT event to re-accept all draggable items and because nothing else is accepted the OUT won't be triggered by other draggables:

$(".drop-zone").droppable({
    drop: function(event, ui) { 
        $(this).droppable('option', 'accept', ui.draggable);
    },
    out: function(event, ui){
        $(this).droppable('option', 'accept', '.drag-item');
        }   
    });
});
Thierry Blais
  • 2,848
  • 22
  • 41
  • I had the same problem as well. Smart solution! Great job! – Trav McKinney Dec 20 '12 at 18:30
  • 9
    Unfortunately, this breaks if the draggable element reverts back to .drop-zone -- e.g. the user drops the element on an invalid zone. – yayitswei Jul 05 '13 at 22:20
  • @yayitswei U̶s̶i̶n̶g̶ ̶'̶o̶v̶e̶r̶:̶'̶ ̶i̶n̶s̶t̶e̶a̶d̶ ̶o̶f̶ ̶'̶o̶u̶t̶'̶ ̶s̶h̶o̶u̶l̶d̶ ̶f̶i̶x̶ ̶i̶t̶.̶ I was wrong.. – Rogoon Oct 17 '16 at 21:30
6

You can destroy the .droppable() widget after the first drop, like this:

$(".droppable").droppable({
    drop: function( event, ui ) {
        $(this).droppable("destroy");
    }
});

You can try out a demo here.

Nick Craver
  • 623,446
  • 136
  • 1,297
  • 1,155
5

I spend many hours to figure it out and finally it works for me like this:

$( ".drop-zone" ).droppable({
    classes: {
        "ui-droppable-active": "ui-state-active",
        "ui-droppable-hover": "ui-state-hover"
    },
    accept: function( draggable ){
        if (!$(this).hasClass('dropped') || draggable.hasClass('dropped')){
            return true;
        }
        return false;
    },
    drop: function( event, ui ) {
        $(this).addClass('dropped');
        ui.draggable.addClass('dropped');
    },
    out: function( event, ui ){
        $(this).removeClass('dropped');
        ui.draggable.removeClass('dropped');
    }
});
Than Ngo Hoai
  • 479
  • 3
  • 10
4

Easy Peasey. Just enable all the .drop-zone's when hovered over them, and then check if the currently hovered .drop-zone contains a draggable element

$('.drop-zone').droppable({
  over: function(event, ui) {

    // Enable all the .droppable elements
    $('.droppable').droppable('enable');

    // If the droppable element we're hovered over already contains a .draggable element, 
    // don't allow another one to be dropped on it
    if($(this).has('.draggable').length) {
        $(this).droppable('disable');
    }
  }
});
Catfish
  • 18,876
  • 54
  • 209
  • 353
2

This solution solves a major bug in Likwid_T's answer.

$('.draggable').draggable({
  start: function(ev, ui) {  
    $('.ui-droppable').each(function(i, el) {
      if (!$(el).find('.ui-draggable').length) $(el).droppable('enable');
    });
  }
});

$('.droppable').droppable({
  drop: function(ev, ui) {
    $(ev['target']).droppable('disable');
  }
});
Harrison Powers
  • 359
  • 1
  • 7
  • 16
1

How about this:

$(".drop-zone").droppable({
    accept: function(draggable) {
        return $(this).find("*").length == 0;
    });
});

This way the accept funcion return true only when no elements have been dropped yet.

Stefano Luoni
  • 81
  • 1
  • 8
0

You could also do it the other way around, by reverting the draggable when the droppable has a certain class or attribute (building on this example: https://stackoverflow.com/a/3418306/1005334).

So for example, using the rel attribute (you could also use class or something else), for the droppable:

$('.drop-zone').droppable({
    drop: function () {
        drop.attr('rel', 'filled');
    }
});

And the draggable:

$('.draggable').draggable({
    revert: function (droppable) {

        if (droppable.attr('rel') == 'filled') {
            return true;
        }
    }
});
Community
  • 1
  • 1
kasimir
  • 1,506
  • 1
  • 20
  • 26
0

My solution is similar to Likwid_T's, except it uses the droppable drop event as well as maintaining the links between draggables and droppables instead of droppable's out event. I think the problem with using out is that it is fired even when a draggable is dragged over an already "full" droppable and then "out" of it.

droppable({
  drop: function(event, ui) {
    var $droppable = $(this);
    var $draggable = ui.draggable;

    // If the draggable is moved from another droppable, unlink it from the old droppable
    var oldDropped = $draggable.data('dropped');
    if(oldDropped) {
      $draggable.data('dropped', null);
      oldDropped.data('dragged', null);
    }

    // Link the draggable and droppable
    $draggable.data('dropped', $droppable);
    $droppable.data('dragged', $draggable);
  },
  accept: function() {
    // Only accept if there is no draggable already associated
    return !$(this).data('dragged');
  }
});

A related feature is that one dragging one item over a droppable that already has a draggable, the old one would get replaced and revert to its initial position. This is how I do it:

droppable({
  drop: function(event, ui) {
    var $droppable = $(this);
    var $draggable = ui.draggable;

    // Reset position of any old draggable here
    var oldDragged = $droppable.data('dragged');
    if(oldDragged) {
      // In the CSS I have transitions on top and left for .ui-draggable, so that it moves smoothly
      oldDragged.css({top: 0, left: 0});
      oldDragged.data('dropped', null);
    }

    // If the draggable is moved from another droppable, unlink it from the old droppable
    var oldDropped = $draggable.data('dropped');
    if(oldDropped) {
      $draggable.data('dropped', null);
      oldDropped.data('dragged', null);
    }

    // Link the draggable and droppable
    $draggable.data('dropped', $droppable);
    $droppable.data('dragged', $draggable);
  },
});
hagabaka
  • 81
  • 3
0

To enable it, use the option: $(".selector").droppable({ disabled: **false** });

jefflunt
  • 33,527
  • 7
  • 88
  • 126
Sergey
  • 240
  • 3
  • 4