I have a little problem with jQuery UI\'s droppable component, but I\'m not quite sure whether I have that problem because of my code or because of a bug in the component.
Recently solved this exact same problem so thought I'd share here.
First thing I'll mention is that a similar issue involving nested droppables and event bubbling is solved here. The solution below solves the issue for NON-nested, but overlapping droppables.
If you review OP's jsfiddle using his instructions, you will notice that both of those DOM elements are independent of each other and live on the same DOM "level". However, as OP and a few others have noticed, jQuery UI's Droppable seems to bubble the drop event to DOM elements with overflowing content underlapping the target droppable.
Set a flag when the element is dropped in the target droppable. Check the flag in the drop event of the underlapping DOM element. If this flag contains the value indicating that it has been dropped in the target droppable, return false, do nothing, or revert. Whatever you want to do to ignore the drop event.
For my particular case, I set my flag via a simple attribute value to ui.helper in the target droppable's drop event like so:
$(ui.helper).attr('ignore-drop-event', "true");
In the bubbled drop event on the underlapping droppable, I check that this attribute is set to true:
if ($(ui.helper).attr('ignore-drop-event') === "true") {
// Ignore this drop event since it occurred as part of event bubbling
}
else {
// This is a "real" drop event
}
This solution relies on the assumption that
Event bubbling order is consistent. Meaning the target droppable will always receive the event prior to the underlapping droppable element. I don't know if this is always true, but this assessment has been accurate in my testing.
The attribute value is defined prior to the flag check on the bubbled drop event.
Hope this helps someone else out there.
You've already accepted an answer, although I thought I should just put it out there:
A more elegant workaround (based on event bubbling and which only really deals with viewability to one level, not recursively) can be made by making $('.box, .item').droppable()
and since by default greedy:false
the nested div's drop event should trigger, followed by the outer div.
A simple element check like hasClass('box')
means that the drop occurred in a valid region, so all you need to do is on the inner drop event cache the element that was dropped into, and then on the outer div's drop event (which happens, as mentioned, only a moment later) do with it whatever.
If you drop outside the outer div, even though the inner div drop event will fire, the outer one wont, so nothing other than a useless cache event happened. The only problem is that it looks like there's a bug with non-greedy nested droppables, the jQuery example http://jqueryui.com/demos/droppable/propagation.html doesn't even work properly for me - it behaves as if it were using event capture and not event bubbling...
The only other (admittedly much more plausible) explanation is that I'm misunderstanding how nested droppables are meant to behave.
Check the droppable element's bounds against the parent container and break the execution of the function if the droppable's bottom is above the parent's top or the droppable's top is beneath the parent's bottom:
$('.item').droppable( {
activeClass: "ui-state-default",
hoverClass: "ui-state-hover",
accept: "#draggable",
drop: function( event, ui ) {
var cTop = $(this).closest(".box").position().top + parseInt($(this).closest(".box").css("margin-top"));
var cBtm = $(this).closest(".box").position().top + parseInt($(this).closest(".box").css("margin-top")) + $(this).closest(".box").height();
var dTop = $(this).position().top + parseInt($(this).css("margin-top"));
var dBtm = $(this).position().top + parseInt($(this).css("margin-top")) + $(this).height()
if (dBtm > cTop && dTop < cBtm) {
alert("Dropped.");
}
}
});
Example: http://jsfiddle.net/lthibodeaux/2p56Y/6/
I realize it's not elegant, but it seems workable. I admit to only cursory testing of this script.