jQuery droppable - получение событий во время перетаскивания (а не только при первоначальном перетаскивании)

Я использую jQuery droppable (вместе с jQuery draggable), чтобы пользователь мог добавлять строки в таблицу HTML, перетаскивая элементы из списка и помещая их в таблицу.

Это работает хорошо, однако в настоящее время логика такова, что когда пользователь перетаскивает строку таблицы, новая строка добавляется ниже строки, на которую он переместил курсор.

Было бы лучше, если бы позиция добавления новой строки зависела от того, попал ли пользователь в верхнюю или нижнюю половину существующей строки.

Это достаточно легко вычислить в событии drop, но мне нужно дать обратную связь пользовательскому интерфейсу, когда пользователь перетаскивает (что я бы сделал, например, с помощью двух классов 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; }

person Richard Ev    schedule 01.06.2011    source источник
comment
Является ли сортировка вариантом, а не обратной связью с границей? то есть при перетаскивании появится место для вставляемой строки   -  person Gary Green    schedule 01.06.2011
comment
@Gary: Спасибо за предложение, но я не думаю, что сортировка мне здесь поможет. Моя проблема связана с добавлением новых строк, а не с сортировкой строк. Мой пользовательский интерфейс действительно позволяет пользователю сортировать строки таблицы после их добавления, и это делается с помощью плагина tableDnD jQuery.   -  person Richard Ev    schedule 01.06.2011
comment
У меня тоже была эта проблема, когда я одновременно использовал сортировку и перетаскивание/удаление. Я отключил сортировку и получил ожидаемое поведение.   -  person Paul Totzke    schedule 11.12.2018


Ответы (5)


Как вы сказали, over (как и его аналог out) поднимается только один раз в выпадающем списке. С другой стороны, событие drag объекта draggable вызывается каждый раз при перемещении мыши и кажется подходящим для задачи. Однако у этой стратегии есть две проблемы:

  • drag поднимается вне зависимости от того, лежит ли перетаскиваемый объект над отбрасываемым,
  • даже в этом случае droppable не передается обработчику событий.

Один из способов решить обе проблемы — связать droppable и draggable в обработчике over, используя средство jQuery data(), и разъединить их в обработчиках out и 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.
        }
    }
});

Теперь, когда draggable знает, над каким droppable он лежит, мы можем обновить внешний вид элемента в обработчике событий 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");
    }
});
person Frédéric Hamidi    schedule 22.06.2011

Я сталкиваюсь с той же проблемой и думаю о двух решениях, которыми я поделюсь, если они дадут направление другим, которые найдут эту относительно редкую потребность.

  • Решение с двумя элементами div: добавьте два элемента div в каждую ячейку строки, расположите их так, чтобы они располагались друг над другом, и каждый из которых имеет высоту 50 % и полную ширину с z-index, установленным на -1, для защиты от помех пользовательского интерфейса. Теперь создайте эти droppables и используйте их события «over» и «out» для переключения классов родительской ячейки или строки.

  • Откажитесь от сбрасываемого и сверните свое собственное обнаружение столкновений: напишите свое собственное обнаружение столкновений, чтобы имитировать эффект сбрасывания. Это дало бы больше свободы, но привело бы к некоторому серьезному кодированию и, следовательно, не для случайных требований. Также будут подвержены проблемам с производительностью. Тем не менее, должны быть некоторые очевидные ярлыки, основанные на прецедентах, которые будут работать в вашу пользу.

Мне было бы интересно услышать о любых других подходах к решению с низким кодом.

person James Evason    schedule 17.11.2011

Я только что столкнулся с этой проблемой. Не так много требуется, если вы просто реализуете проверку попадания в событии «перетаскивания». Здесь я только что пометил все свои цели перетаскивания с помощью .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');
    }
});
person Daniel Earwicker    schedule 14.05.2012

Другой способ — добавить класс или другой выборщик к элементу подсказки. Затем в перетаскиваемом определении в обработчике перетаскивания обновите позицию подсказки:

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

});
person NoelHunter    schedule 09.07.2014

Это довольно грубое (и бескодовое) решение, но вы можете попробовать использовать параметр hoverClass с вашим Droppable и создать новый класс под названием «hovering», чтобы задать поведение Droppable, которое происходит только тогда, когда Draggable зависает над Droppable. Затем этот «зависающий» класс может (это грубо) запускать какой-то бесконечный цикл или какую-то другую проверку; Я не использовал эти классы раньше, поэтому я не могу придумать никаких подробностей после этого момента. знак равно

Редактировать: еще грубее, вы можете чередовать отключение и включение Droppable с помощью класса «зависание»; Я бы определенно сделал это синхронно, а также с большим разграничением времени. Чередующиеся вызовы отключения и включения должны инициировать одно из событий, с которым вам придется поэкспериментировать, чтобы выяснить.

person sichinumi    schedule 01.06.2011