Reputation: 876
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.
I have a div with a fixed width and height. The overflow-x for that div is set to hidden, overflow-y is set to auto. Within that div I have some more div's. So many of them that the outer div starts scrolling. Each of the inner divs is a droppable, accepting a draggable which is outside the wrapper div.
If I drag & drop the draggable item somewhere within the wrapper, everything works fine. The problem is that the drop event gets even triggered if I drop the element shortly below the wrapper div.
I'm not really good at explaining the problem; therefore here is some code which reproduces the problem:
Simply drag and drop the "Drag Me!" container below the div with the scrollbar. Unexpectedly you will see the alert "dropped".
Now something interesting: If you scroll down to item "Test28" and now you drag and drop the draggable below the wrapper, the drop event won't be triggered. It looks like the hidden elements are still accessible when you drop something on them.
So, is this a bug or do I need to write my code differently to make it work? (or both? :-) )
Upvotes: 10
Views: 5769
Reputation: 623
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.
Upvotes: 0
Reputation: 45555
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.
Upvotes: 3
Reputation: 4992
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.
Upvotes: 3