Search code examples
jqueryjquery-uidrag-and-dropdraggable

Over event isn't propagated if nested droppable doesn't accept draggable under a certain scenario


The steps below are applicable to the jsFiddle shared below.

Steps:

  1. Click on and drag Draggable 1 onto the inner droppable of Droppable 3. Continue to hold Draggable 1, do not release it.
  2. Note that when Draggable 1 is on the inner droppable and the pointer is fully over the area of Droppable 3, the outer Droppable 3 is highlighted, which is expected.
  3. Move Draggable 1 beyond the bounds of Droppable 3 and release it so that it's resting entirely in whitespace.
  4. Click and drag Draggable 2. You only have to drag it to some random whitespace spot, not on a droppable.
  5. Release Draggable 2.
  6. Grab Draggable 1 again and drag it onto the inner droppable of Draggable 3. Continue to hold Draggable 1, do not release it.
  7. When Draggable 1 is on the inner droppable and the pointer is fully over the area of Droppable 3, take a note of the state of the outer Droppable 3 container.

Expected results:

  • The outer Droppable 3 container is highlighted when Draggable 1 is entirely on the inner droppable and within the bounds of Droppable 3.

Actual results:

  • The outer Droppable 3 container is not highlighted when Draggable 1 is entirely on the inner droppable and within the bounds of Droppable 3.

Notes:

  • If you debug the issue, it turns out that simply dragging Draggable 2 results in some state changes in Droppable 3, such that event propagation no longer works as expected when Droppable 1 is on the inner droppable.
  • Console logs have been added to this jsFiddle to help highlight this issue.

Related Question:

I'm asking this question because it seems somewhat related to the following StackOverflow question, but my scenario is slightly different. Perhaps it has the same root cause, but there might be a different workaround?

jsFiddle:

https://jsfiddle.net/deepankarj/60zz9zgb/

$(function() {
    $("#draggable, #draggable2").draggable({
        cursor: "move",
        cursorAt: {
            top: 0,
            left: 0
        },
    });

    function logEventId(prefix, event) {
        console.log(prefix + ": " + event.target.id); 
    }
    function createLogEventIdFunction(handlerName) {
        return function(event, ui) {
            logEventId(handlerName, event);
        }
    }

    $("#droppable, #droppable-inner").droppable({
        activeClass: "ui-state-hover",
        hoverClass: "ui-state-active",
        tolerance: "pointer",
        over: createLogEventIdFunction("over"),
        out: createLogEventIdFunction("out"),
        drop: function(event, ui) {
            logEventId("drop", event);
            $(this)
                .addClass("ui-state-highlight")
                .find("> p")
                .html("Dropped!");
            return false;
        }
    });

    function decorateWithDroppable(selector, greedy, canAccept, over) {
        canAccept = !!canAccept ? canAccept : function(element) {
            console.log("canAccept: this: " + $(this).attr("id") + ", el: " + element.attr("id") + ", result: true");
            return true;
        };

        over = !!over ? over : createLogEventIdFunction("over");

        $(selector).droppable({
            greedy: greedy,
            activeClass: "ui-state-hover",
            hoverClass: "ui-state-active",
            tolerance: "pointer",
            accept: canAccept,
            over: over,
            out: createLogEventIdFunction("out"),
            drop: function(event, ui) {
                logEventId("drop", event);
                $(this)
                    .addClass("ui-state-highlight")
                    .find("> p")
                    .html("Dropped!");
            }
        });
    };

    decorateWithDroppable("#droppable2, #droppable2-inner", true);
    decorateWithDroppable("#droppable3", true);
    decorateWithDroppable("#droppable3-inner", true, function canAccept(element) {
        console.log("canAccept: this: " + $(this).attr("id") + ", el: " + element.attr("id") + ", result: " + (element.attr("id") !== "draggable"));    
        return (element.attr("id") !== "draggable");
    });

});

Solution

  • Setting refreshPositions to true when initializing draggable works for me. Here is the sample code.

    jQuery().draggable({
        ....    
        refreshPositions: true
        ....
    });