javascript - ejemplos - drag and drop jquery example
jQuery droppable: recepción de eventos durante el arrastre(no solo en el arrastre inicial) (5)
Estoy usando jQuery droppable (junto con jQuery draggable ) para permitir que el usuario agregue filas a una tabla HTML arrastrando elementos de una lista y colocándolos en la tabla.
Esto funciona bien, sin embargo, en la actualidad, la lógica es que cuando el usuario arrastra y suelta en una fila de la tabla, la nueva fila se agrega debajo de la fila en la que se colocó.
Sería mejor si la posición de adición de la nueva fila se basara en si el usuario cayó en la mitad superior o inferior de una fila existente.
Esto es bastante fácil de calcular en el evento drop
, pero necesito dar retroalimentación de UI cuando el usuario arrastra (lo que haría por medio de dos clases de CSS droppable-above
droppable-below
y droppable-below
por ejemplo).
Esto no parece ser posible, ya que el evento over
solo se dispara una vez; cuando el usuario inicialmente arrastra sobre el elemento droppable.
¿Es posible hacer que se dispare el evento over
para cada movimiento del mouse mientras el usuario está sobre un elemento dropable?
Si es así, entonces podría hacer esto:
$("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
}
}
});
Los estilos CSS serían algo así como ...
droppable-above { border-top: solid 3px Blue; }
droppable-below { border-bottom: solid 3px Blue; }
Acabo de golpear este problema. No se requiere mucho si solo implementas pruebas de impacto en el evento ''arrastrar''. Aquí acabo de etiquetar todos mis objetivos de .myDropTarget
con .myDropTarget
, por lo que es fácil encontrarlos todos, recorrerlos y verificar si el mouse está sobre ellos.
Algo como esto:
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'');
}
});
Como dijiste, over
(como su contraparte) solo se levanta una vez en el dropable. Por otro lado, el evento de drag
del elemento que se puede arrastrar se levanta cada vez que se mueve el mouse y parece apropiado para la tarea. Hay, sin embargo, dos problemas con esta estrategia:
-
drag
se levanta tanto si el elemento arrastrable está sobre un droppable como si no - incluso en ese caso, el dropable no se pasa al controlador de eventos.
Una forma de resolver ambos problemas es asociar el dropable y el arrastrable en el manipulador en over
, utilizando la función data() jQuery, y desasociarlos en los manejadores de out
y arrastre:
$("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.
}
}
});
Ahora que el elemento arrastrable sabe lo que se puede drag
, podemos actualizar la apariencia del elemento en un controlador de eventos de 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");
}
}
}
});
El código que sigue es un caso de prueba simple que demuestra esta solución (básicamente, llena los huecos comentados anteriormente y refacta los patrones comunes en funciones de ayuda). La configuración de dropable es un poco más intrincada que en el ejemplo anterior, principalmente porque las filas de tablas recién creadas tienen que ser descartables como sus hermanos.
Puedes ver los resultados en este violín .
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");
}
});
Esta es una solución bastante cruda (y sin código), pero podría intentar usar la opción hoverClass con su Droppable y crear una nueva clase llamada "flotando" para establecer el comportamiento de Droppable que solo ocurre cuando el Draggable está sobre el Droppable. Esta clase "flotante" podría entonces (este es el bit en bruto) ejecutar algún tipo de bucle sin fin o algún otro tipo de verificador; No he usado estas clases antes, así que no puedo pensar en más detalles más allá de este punto. = /
Edición: incluso más cruda, podría alternar la desactivación y la habilitación de Droppable utilizando la clase "flotando"; Definitivamente lo haría de forma sincrónica, y también con una delineación de tiempo generosa. La alternancia de inhabilitar y habilitar llamadas debería desencadenar uno de los eventos, sin embargo, con cuál tendrá que experimentar para averiguarlo.
Otro método es agregar una clase u otro elector al elemento de sugerencia. Luego, en la definición que se puede arrastrar, en el controlador de arrastre, actualice la posición de la pista:
$(''#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);
}
});
Tengo el mismo problema y he estado pensando en dos soluciones que compartiré en caso de que den orientación a otros que encuentran esta necesidad relativamente rara.
Solución de dos div: agregue dos divs en cada celda de la fila, posicionados para ser apilados y cada uno con 50% de alto y ancho completo con el índice z establecido en -1 para proteger de la interferencia de la interfaz de usuario. Ahora haga estos droppables y use sus eventos ''over'' y ''out'' para alternar las clases de la celda o fila principal.
Abandonar y soltar su propia detección de colisiones: escriba su propia detección de colisiones para imitar el efecto de la gota. Esto daría más libertad pero conduciría a una codificación seria y por lo tanto no es para el requisito casual. También sería susceptible a problemas de rendimiento. Dicho esto, debería haber algunos atajos obvios basados en casos que funcionen a tu favor.
Me interesaría saber de cualquier otro enfoque de la solución de código bajo.