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

5 回答 5

30

正如你所说,over(就像它的对应物一样out)只在 droppable 上被提升一次。另一方面,每次鼠标移动时都会引发可拖动drag事件,并且似乎适合该任务。然而,这种策略存在两个问题:

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

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

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

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

你可以在这个 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");
    }
});
于 2011-06-22T15:30:46.837 回答
2

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

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

  • 放弃 droppable 并滚动您自己的碰撞检测:编写您自己的碰撞检测来模拟 droppable 效果。这将提供更多自由,但会导致一些严重的编码,因此不适用于临时要求。也容易受到性能问题的影响。也就是说,应该有一些明显的基于案例的捷径对你有利。

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

于 2011-11-17T10:12:13.420 回答
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');
    }
});
于 2012-05-14T14:13:22.123 回答
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);
        }

});
于 2014-07-09T01:49:37.383 回答
0

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

编辑:甚至更粗略,您可以使用“悬停”类交替禁用和启用 Droppable;不过,我肯定会同步执行此操作,并且还会有大量的时间划分。交替禁用和启用调用触发其中一个事件,但您必须尝试找出哪个事件。

于 2011-06-01T21:29:52.657 回答