jQuery droppable - 在拖动期间接收事件(不仅仅是在初始拖动时)

发布于 2024-11-11 14:45:10 字数 1512 浏览 5 评论 0原文

我正在使用 jQuery droppable (与 jQuery Draggable) 允许用户通过从列表中拖动项目并将其放到表格上来将行添加到 HTML 表格中。

这很有效,但目前的逻辑是,当用户在表行上拖放时,新行将添加到他们拖放的行下方。

如果新行的添加位置基于用户是否放置在现有行的上半部分或下半部分,那就更好了。

这在 drop 事件中很容易计算,但我需要在用户拖动时提供 UI 反馈(我将通过两个 CSS 类 droppable-above 来实现)例如,droppable-below)。

这似乎不可能,因为 over 事件仅触发一次;当用户最初拖动可放置元素时。

当用户位于可放置元素上方时,是否可以为每次鼠标移动触发 over 事件?

如果是这样,那么我就可以这样做:

$("tr.droppable").droppable({
    over: function(event, ui) {
        if (/* mouse is in top half of row */) {
            $(this).addClass("droppable-above").removeClass("droppable-below");
        }
        else {
            $(this).removeClass("droppable-above").addClass("droppable-below");
        }
    },

    out: function(event, ui) {
        $(this).removeClass("droppable-above").removeClass("droppable-below");
    },

    drop: function(event, ui) {
        $(this).removeClass("droppable-above").removeClass("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
        }
    }
});

CSS 样式将类似于......

droppable-above { border-top: solid 3px Blue; }
droppable-below { border-bottom: solid 3px Blue; }

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.

This works well, however at present the logic is that when the user drag-drops on a table row the new row gets added below the row they dropped on.

It would be better if the new row's add position was based on whether the user dropped in the upper or lower half of an existing row.

This is easy enough to calculate in the drop event, but I need to give UI feedback as the user drags (which I would do by means of two CSS classes droppable-above and droppable-below for example).

This doesn't seem to be possible, as the over event only fires once; when the user initially drags over the droppable element.

Is it possible to get the over event to fire for every mouse move while the user is over a droppable element?

If so, then I'd be able to do this:

$("tr.droppable").droppable({
    over: function(event, ui) {
        if (/* mouse is in top half of row */) {
            $(this).addClass("droppable-above").removeClass("droppable-below");
        }
        else {
            $(this).removeClass("droppable-above").addClass("droppable-below");
        }
    },

    out: function(event, ui) {
        $(this).removeClass("droppable-above").removeClass("droppable-below");
    },

    drop: function(event, ui) {
        $(this).removeClass("droppable-above").removeClass("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
        }
    }
});

The CSS styles would be something like...

droppable-above { border-top: solid 3px Blue; }
droppable-below { border-bottom: solid 3px Blue; }

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

放低过去 2024-11-18 14:45:10

正如您所说,over(就像其对应的out)仅在可放置对象上引发一次。另一方面,每次鼠标移动时,draggabledrag 事件都会引发,并且似乎适合该任务。然而,这种策略有两个问题:

  • 无论可拖动对象是否实际上位于可放置对象之上,都会引发 drag
  • 即使在这种情况下,可放置对象也不会传递给事件处理程序。

解决这两个问题的一种方法是使用 jQuery 的 data() 在 over 处理程序中关联 droppable 和draggable 设施,并在 outdrop 处理程序中取消它们的关联:

$("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.
        }
    }
});

现在,draggable 知道它所在的 droppable,我们可以在 a 中更新元素的外观drag 事件处理程序:

$(".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");
            }
        }
    }
});

下面的代码是一个简单的测试用例,演示了此解决方案(它基本上填补了上面注释的空白,并将常见模式重构为辅助函数)。可删除的设置比前面的示例稍微复杂一些,主要是因为新创建的表行必须像其兄弟表一样可删除。

您可以在这个小提琴中查看结果。

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");
    }
});

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");
    }
});
被你宠の有点坏 2024-11-18 14:45:10

我遇到了同样的问题,并且一直在考虑两种解决方案,我将分享这两种解决方案,以防它们为发现这种相对罕见需求的其他人提供指导。

  • 两个 div 解决方案:在行的每个单元格中添加两个 div,定位为堆叠,每个 50% 高和全宽,z-index 设置为 -1 以防止 UI 干扰。现在制作这些 droppables 并使用它们的“over”和“out”事件来切换父单元格或行的类。

  • 放弃可放置并推出您自己的碰撞检测:编写您自己的碰撞检测来模仿可放置效果。这将提供更多的自由,但会导致一些严肃的编码,因此不适合临时需求。也容易受到性能问题的影响。也就是说,应该有一些明显的基于案例的快捷方式对您有利。

我有兴趣了解任何其他低代码解决方案的方法。

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.

べ映画 2024-11-18 14:45:10

我刚刚遇到这个问题。如果您只是在“拖动”事件中实现命中测试,则不需要做太多事情。在这里,我刚刚用 .myDropTarget 标记了所有放置目标,因此很容易找到所有它们,循环遍历它们并检查鼠标是否位于它们上方。

像这样的东西:

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');
    }
});

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');
    }
});
翻了热茶 2024-11-18 14:45:10

另一种方法是将类或其他选择器添加到提示元素。然后在可拖动定义中的拖动处理程序上,更新提示位置:

$('#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);
        }

});

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);
        }

});
网白 2024-11-18 14:45:10

这是一个相当粗糙(且无代码)的解决方案,但您可以尝试在 Droppable 中使用hoverClass选项,并创建一个名为“hovering”的新类来设置仅当 Draggable 悬停在 Droppable 上时才会发生的 Droppable 行为。然后,这个“悬停”类可以(这是粗略的部分)运行某种无限循环或某种其他类型的检查器;我以前没有使用过这些类,所以我想不出更多的细节。 =/

编辑:更简单的是,您可以使用“悬停”类交替禁用和启用 Droppable ;不过,我肯定会同步执行此操作,并且还会划出大量的时间。交替禁用和启用调用应该触发其中一个事件,但您必须尝试一下才能找到答案。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文