Подтвердить что ты не робот

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

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

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

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

Это достаточно просто вычислить в событии drop, но мне нужно дать обратную связь от пользователя, когда пользователь перетаскивает (что я бы сделал с помощью двух классов CSS droppable-above и droppable-below, например).

Это не представляется возможным, поскольку событие over запускается только один раз; когда пользователь сначала перетаскивает элемент droppable.

Можно ли запустить событие 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; }
4b9b3361

Ответ 1

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

  • drag выражается, действительно ли перетаскиваемый объект лежит над droppable,
  • даже в этом случае 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");
    }
});

Ответ 2

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

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

  • Abandon droppable и roll your own collision detection: Напишите свое собственное обнаружение столкновений, чтобы имитировать эффект droppable. Это даст больше свободы, но приведет к некоторому серьезному кодированию и, следовательно, не соответствует случайным требованиям. Также было бы восприимчиво к проблемам с производительностью. Тем не менее, должны быть некоторые очевидные ярлыки на основе случаев, которые будут работать в вашу пользу.

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

Ответ 3

Я просто ударил эту проблему. Не очень много требуется, если вы просто применяете тестирование на удар в событии "drag". Здесь я только что пометил все мои целевые объекты с помощью .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');
    }
});

Ответ 4

Другим методом является добавление класса или другого электората в элемент подсказки. Затем в перетаскиваемом определении на обработчике перетаскивания обновите положение подсказки:

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

});

Ответ 5

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

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