23

Estoy usando jQuery droppable (junto con jQuery draggable) para permitir al usuario agregar filas a una tabla HTML arrastrando elementos desde una lista y soltándolos la mesa.jQuery droppable - recibir eventos durante el arrastre (no solo en el arrastre inicial)

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 en la fila que soltaron.

Sería mejor si la posición de agregar 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 caso drop, pero tengo que dar información de interfaz de usuario que el usuario arrastra (que lo haría por medio de dos clases CSS droppable-above y droppable-below por ejemplo).

Esto no parece posible, ya que el evento over solo se dispara una vez; cuando el usuario arrastra inicialmente el elemento que se puede quitar.

¿Es posible activar el evento over para cada movimiento del mouse mientras el usuario está sobre un elemento que se puede soltar?

Si es así, entonces yo sería capaz de 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ía algo así como ...

droppable-above { border-top: solid 3px Blue; } 
droppable-below { border-bottom: solid 3px Blue; } 
+0

¿Se puede elegir una opción en lugar de la información del borde? es decir, cuando lo arrastre, aparecerá espacio para que se inserte la fila –

+0

@Gary: gracias por la sugerencia, pero no creo que los sortables me ayuden aquí. Mi problema es específico para agregar nuevas filas, en lugar de ordenar las filas. Mi UI realmente permite al usuario ordenar las filas de la tabla después de agregarlas, y esto se hace usando el plugin jDuend de tablaDnD. –

Respuesta

28

Como dijiste, over (como su contraparte out) solo se levanta una vez en la lista desplegable. Por otro lado, el evento drag del que se puede arrastrar se genera cada vez que se mueve el mouse, y parece apropiado para la tarea. Hay, sin embargo, dos problemas con esta estrategia:

  • drag se eleva o no la pueda arrastrar en realidad se encuentra en un lanzables,
  • incluso en ese caso, el lanzables no se pasa al controlador de eventos.

Una manera de resolver ambos problemas es asociar la lanzables y de la que pueden arrastrarse en el controlador over, utilizando las instalaciones de jQuery data(), y disociar en las out y drop manipuladores:

$("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 la arrastrable conoce el lanzables que está mintiendo otra vez, podemos actualizar la apariencia del elemento dentro de un manejador de eventos 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 vacíos comentados anteriormente y refactores patrones comunes en funciones auxiliares). La configuración desplegable es un poco más complicada que en el ejemplo anterior, principalmente porque las filas de la tabla recién creadas deben ser desplegables como sus hermanos.

Puede ver los resultados en this 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"); 
    } 
}); 
+0

ty muchísimo Frédéric Hamidi! una solución limpia y simple al problema + buena explicación y demostración (jsfiddle), ¡no podría pedir más! – loostro

+0

Usted señor, es mi héroe. :) –

0

Ésta es una bastante crudo (y sin código) solución, pero podría intentar usar la opción hoverClass con su Droppable y crear una nueva clase llamada "hovering" para establecer el comportamiento Droppable que solo ocurre cuando el Draggable está sobrevolando el Droppable. Esta clase "suspendida" podría (este es el bit crudo) ejecutar algún tipo de bucle sin fin o algún otro tipo de corrector; No he usado estas clases antes, así que no puedo pensar en más detalles más allá de este punto. =/

Editar: Aún más crudo, podrías alternar deshabilitando y habilitando el Droppable usando la clase "hovering"; Me gustaría definitivamente hacerlo sincrónicamente, y con una delineación de tiempo generosa también. La alternancia de deshabilitar y habilitar las llamadas debe activar uno de los eventos, aunque con el que tendrá que experimentar para averiguarlo.

2

estoy golpeando el mismo tema y he estar es pensar en dos soluciones que compartiré en caso de que den dirección a otros que encuentran esta necesidad relativamente rara.

  • Dos solución div: Añadir dos divs en cada celda de la fila, situado para ser apilados y cada 50% de alto y de ancho completo con z-index establece en -1 para proteger de la interferencia de interfaz de usuario. Ahora haga estos marcadores desplegables y use sus eventos 'sobre' y 'fuera' para alternar las clases de la celda o fila padre.

  • Abandone su detección de colisiones y arrastre su propia colisión: escriba su propia detección de colisiones para imitar el efecto que se puede soltar. Esto daría más libertad pero conduciría a una codificación seria, por lo que 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 funcionarían a su favor.

Me gustaría saber de cualquier otro enfoque para la solución de bajo código.

1

Acabo de solucionar este problema. No se requiere mucho si solo implementa pruebas de impacto en el evento 'arrastrar'. Aquí acabo de etiquetar todos mis destinos desechables con .myDropTarget, por lo que es fácil encontrarlos a 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'); 
    } 
}); 
1

Otro método consiste en añadir una clase u otro elector al elemento de pista.Luego, en la definición arrastrable, en el controlador de arrastre, actualice la posición de sugerencia:

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

}); 
Cuestiones relacionadas