jQuery droppable - 在拖动过程中接收事件(不仅在初始拖动时)

24

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

这很有效,但目前的逻辑是:当用户在表格行上拖放时,新行会被添加到它们所停靠的下方

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

drop事件中计算这一点很容易,但我需要在用户拖动时给出UI反馈(例如通过两个CSS类droppable-abovedroppable-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; }

可否使用sortable作为边框反馈的选项?例如,当您拖动时,它会显示出为插入行腾出空间的样式。 - Gary Green
@Gary:感谢您的建议,但我不认为可排序表格能帮助我解决问题。我的问题是特定于添加新行,而不是排序行。实际上,我的用户界面允许用户在添加行后对表格行进行排序,这是使用tableDnD jQuery插件完成的。 - Richard Ev
当我同时使用sortable和draggable/droppable时,我也遇到了这个问题。我禁用了sortable,然后获得了我期望的行为。 - Paul Totzke
5个回答

30

正如你所说,over(像它的同伴 out 一样)只会在可放置元素上触发一次。另一方面,可拖动元素drag 事件会在每次鼠标移动时触发,并且似乎适合这个任务。但是,这种策略存在两个问题:

  • drag 会在可拖动元素实际上不在可放置元素上时触发,
  • 即使在这种情况下,可放置元素也不会被传递到事件处理程序中。

解决这两个问题的方法之一是,在 over 处理程序中使用 jQuery 的 data() 工具将可放置元素和可拖动元素关联起来,并在 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.
        }
    }
});

现在可拖动的元素已经知道了它所处于的放置区,我们可以在 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");
            }
        }
    }
});

下面的代码是一个简单的测试用例,演示了这个解决方案(基本上填充了上面的注释间隙并将常见模式重构为帮助函数)。可拖动设置比前一个示例更为复杂,主要是因为新创建的表格行必须像它们的同级元素一样具有可拖动性。你可以在这个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");
    }
});

2

我遇到了同样的问题,思考了两种解决方案,分享给其他遇到这个相对罕见需求的人。

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

  • 放弃 droppable 并自己编写碰撞检测: 编写自己的碰撞检测以模仿 droppable 效果。这会给予更多自由,但需要进行一些严肃的编码,因此不适用于普通需求。也容易出现性能问题。话虽如此,应该有一些明显的基于情况的快捷方式可以帮助你。

我很想听听其他低代码解决方案的方法。


1

我刚遇到了这个问题。如果你只在“拖动”事件中实现点击测试,那么所需的工作量不会太大。在这里,我只是用.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');
    }
});

1
另一种方法是向提示元素添加类或其他选择器。然后在可拖动定义中,在拖动处理程序上更新提示位置。
$('#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

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

编辑:更加粗糙,您可以交替使用 "hovering" 类来禁用和启用 Droppable;我会 绝对 同步执行此操作,并且也要有充足的时间划分。交替的禁用和启用调用应该触发其中一个事件,但您必须进行实验才能找到哪一个事件。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接