Emil Avramov
Emil Avramov

Reputation: 921

jQuery UI - Droppable only accept one draggable

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

Upvotes: 11

Views: 23918

Answers (9)

Than Ngo Hoai
Than Ngo Hoai

Reputation: 469

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');
    }
});

Upvotes: 5

hagabaka
hagabaka

Reputation: 81

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);
  },
});

Upvotes: 0

Harrison Powers
Harrison Powers

Reputation: 359

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');
  }
});

Upvotes: 2

Catfish
Catfish

Reputation: 19312

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');
    }
  }
});

Upvotes: 4

kasimir
kasimir

Reputation: 1554

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;
        }
    }
});

Upvotes: 0

Stefano Luoni
Stefano Luoni

Reputation: 79

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.

Upvotes: 1

Thierry Blais
Thierry Blais

Reputation: 2858

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');
        }   
    });
});

Upvotes: 29

Sergey
Sergey

Reputation: 240

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

Upvotes: 0

Nick Craver
Nick Craver

Reputation: 630597

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.

Upvotes: 6

Related Questions