jQuery droppable - receiving events during drag over (not just on initial drag over)

前端 未结 5 1806
忘掉有多难
忘掉有多难 2021-01-31 09:50

I am using jQuery droppable (in conjunction with jQuery draggable) to allow the user to add rows to an HTML table by dragging items from a list and dropping them on the table.

5条回答
  •  栀梦
    栀梦 (楼主)
    2021-01-31 10:29

    As you said, over (like its counterpart out) is only raised once on the droppable. On the other hand, the drag event of the draggable is raised every time the mouse moves, and seems appropriate for the task. There are, however, two problems with this strategy:

    • drag is raised whether or not the draggable actually lies over a droppable,
    • even in that case, the droppable is not passed to the event handler.

    One way to solve both problems is to associate the droppable and the draggable in the over handler, using jQuery's data() facility, and disassociate them in the out and drop handlers:

    $("tr.droppable").droppable({
        over: function(event, ui) {
            if (/* mouse is in top half of row */) {
                $(this).removeClass("droppable-below")
                       .addClass("droppable-above");
            }
            else {
                $(this).removeClass("droppable-above")
                       .addClass("droppable-below");
            }
            ui.draggable.data("current-droppable", $(this));  // Associate.
        },
    
        out: function(event, ui) {
            ui.draggable.removeData("current-droppable");     // Break association.
            $(this).removeClass("droppable-above droppable-below");
        },
    
        drop: function(event, ui) {
            ui.draggable.removeData("current-droppable");     // Break association.
            $(this).removeClass("droppable-above droppable-below");
            if (/* mouse is in top half of row */) {
                // Add new row above the dropped row.
            }
            else {
                // Add new row below the dropped row.
            }
        }
    });
    

    Now that the draggable knows the droppable it's lying over, we can update the element's appearance in a drag event handler:

    $(".draggable").draggable({
        drag: function(event, ui) {
            var $droppable = $(this).data("current-droppable");
            if ($droppable) {
                if (/* mouse is in top half of row */) {
                    $droppable.removeClass("droppable-below")
                              .addClass("droppable-above");
                } else {
                    $droppable.removeClass("droppable-above")
                              .addClass("droppable-below");
                }
            }
        }
    });
    

    The code that follows is a simple test case that demonstrates this solution (it basically fills the commented gaps above and refactors common patterns into helper functions). The droppable setup is a little more intricate than in the previous example, mainly because the newly created table rows have to be made droppable like their siblings.

    You can see the results in this fiddle.

    HTML:

    New item 1
    New item 2
    New item 3
    New item 4
    New item 5

    Drag the items above into the table below.

    Item 1
    Item 2
    Item 3
    Item 4
    Item 5

    CSS:

    p {
        line-height: 32px;
    }
    
    table {
        width: 100%;
    }
    
    .draggable {
        background-color: #d0ffff;
        border: 1px solid black;
        cursor: pointer;
        padding: 6px;
    }
    
    .droppable {
        background-color: #ffffd0;
        border: 1px solid black;
    }
    
    .droppable td {
        padding: 10px;
    }
    
    .droppable-above {
        border-top: 3px solid blue;
    }
    
    .droppable-below {
        border-bottom: 3px solid blue;
    }
    

    Javascript:

    $(document).ready(function() {
        $(".draggable").draggable({
            helper: "clone",
            drag: function(event, ui) {
                var $droppable = $(this).data("current-droppable");
                if ($droppable) {
                    updateHighlight(ui, $droppable);
                }
            }
        });
    
        initDroppable($(".droppable"));
    
        function initDroppable($elements)
        {
            $elements.droppable({
                over: function(event, ui) {
                    var $this = $(this);
                    updateHighlight(ui, $this);
                    ui.draggable.data("current-droppable", $this);
                },
                out: function(event, ui) {
                    cleanupHighlight(ui, $(this));
                },
                drop: function(event, ui) {
                    var $this = $(this);
                    cleanupHighlight(ui, $this);
                    var $new = $this.clone().children("td:first")
                                    .html(ui.draggable.html()).end();
                    if (isInUpperHalf(ui, $this)) {
                        $new.insertBefore(this);
                    } else {
                        $new.insertAfter(this);
                    }
                    initDroppable($new);
                }
            });
        }
    
        function isInUpperHalf(ui, $droppable)
        {
            var $draggable = ui.draggable || ui.helper;
            return (ui.offset.top + $draggable.outerHeight() / 2
                    <= $droppable.offset().top + $droppable.outerHeight() / 2);
        }
    
        function updateHighlight(ui, $droppable)
        {
            if (isInUpperHalf(ui, $droppable)) {
                $droppable.removeClass("droppable-below")
                          .addClass("droppable-above");
            } else {
                $droppable.removeClass("droppable-above")
                          .addClass("droppable-below");
            }
        }
    
        function cleanupHighlight(ui, $droppable)
        {
            ui.draggable.removeData("current-droppable");
            $droppable.removeClass("droppable-above droppable-below");
        }
    });
    

提交回复
热议问题