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

前端 未结 5 1795
忘掉有多难
忘掉有多难 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:24

    Another method is to add a class or other elector to the hint element. Then in the draggable definition, on the drag handler, update the hint position:

    $('#dropArea').droppable({
            over: function(event, ui) 
                // Create a clone 50 pixels above and 100 to the left of drop area 
                $('#someHint').clone()
                        .css({
                            position: 'absolute',
                            top: ui.offset.top+50,
                            left: ui.offset.left-50
                        })
                        .addClass("clone")          // Mark this as a clone, for hiding on drop or out
                        .addClass("dragHelper")     // Mark this as a hint, for moving with drag
                        .appendTo(document.body)
    
    
            },
            out: function(event, ui) {
                $('.clone').remove();                       // Remove any hints showing
            },
            drop: function(event, ui) {
                $('.clone').remove();                       // Remove any hints showing
            }
        });
    
    $("#mydiv").draggable({
            drag: function(event, ui) {
                $('.dragHelper').css('left',ui.offset.left);
                $('.dragHelper').css('top',ui.offset.top);
            }
    
    });
    
    0 讨论(0)
  • 2021-01-31 10:25

    This is a rather crude (and codeless) solution, but you could try using the hoverClass option with your Droppable and creating a new class called "hovering" to set Droppable behavior that only happens when the Draggable is hovering over the Droppable. This "hovering" class could then (this is the crude bit) run some sort of endless loop or some other sort of checker; I haven't used these classes before, so I can't think of any more specifics past this point. =/

    Edit: Even cruder, you could alternate disabling and enabling the Droppable using the "hovering" class; I would definitely do this synchronously though, and with a generous time delineation as well. The alternating disable and enable calls should trigger one of the events, though which one you'll have to experiment with to find out.

    0 讨论(0)
  • 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:

    <div class="draggable">New item 1</div>
    <div class="draggable">New item 2</div>
    <div class="draggable">New item 3</div>
    <div class="draggable">New item 4</div>
    <div class="draggable">New item 5</div>
    <p>Drag the items above into the table below.</p>
    <table>
        <tr class="droppable"><td>Item 1</td></tr>
        <tr class="droppable"><td>Item 2</td></tr>
        <tr class="droppable"><td>Item 3</td></tr>
        <tr class="droppable"><td>Item 4</td></tr>
        <tr class="droppable"><td>Item 5</td></tr>
    </table>
    

    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");
        }
    });
    
    0 讨论(0)
  • 2021-01-31 10:35

    I just hit this problem. Not much is required if you just implement hit testing in the 'drag' event. Here I've just tagged all my drop targets with .myDropTarget, so it's easy to find them all, loop through them and check whether the mouse is over them.

    Something like this:

    thingToBeDragged.draggable({
        drag: function(evt) {
    
            $('.myDropTarget').removeClass('topHalf bottomHalf').each(function() {
                var target = $(this), o = target.offset(),
                    x = evt.pageX - o.left, y = evt.pageY - o.top;
    
                if (x > 0 && y > 0 && x < target.width() && y < target.height()) {
    
                    // mouse is over this drop target, but now you can get more
                    // particular: is it in the top half, bottom half, etc.
    
                    if (y > target.height() * 0.5) {
                        target.addClass('topHalf');
                    } else {
                        target.addClass('bottomHalf');
                    }
                }
            });
        },
        stop: function() {
            var droppedOn = $('.topHalf, .bottomHalf');
        }
    });
    
    0 讨论(0)
  • 2021-01-31 10:42

    I am hitting the same issue and have been thinking about two solutions which I will share in case they give direction to others who find this relatively rare need.

    • Two div solution: Add two divs into each cell of the row, positioned to be stacked and each 50% high and full width with z-index set to -1 to protect from UI interference. Now make these droppables and use their 'over' and 'out' events to toggle the classes of the parent cell or row.

    • Abandon droppable and roll your own collision detection: Write your own collision detection to mimic the droppable effect. This which would give more freedom but would lead to some serious coding and so is not for the casual requirement. Would also be susceptible to performance issues. That said, there should be some obvious case-based shortcuts that would work in your favour.

    I would be interested to hear of any other approaches to low-code solution.

    0 讨论(0)
提交回复
热议问题