Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

jQuery droppable and scrollable divs

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:

http://jsfiddle.net/2p56Y/

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? :-) )

like image 608
StefanS Avatar asked Feb 05 '11 16:02

StefanS


2 Answers

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.

like image 107
lsuarez Avatar answered Sep 28 '22 23:09

lsuarez


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.

like image 24
davin Avatar answered Sep 28 '22 23:09

davin