Prevención de eventos de clic con jQuery drag and drop

85

Tengo elementos en la página que se pueden arrastrar con jQuery. ¿Estos elementos tienen un evento de clic que navega a otra página (enlaces normales, por ejemplo)?

¿Cuál es la mejor manera de evitar que el clic se dispare al soltar dicho elemento mientras se permite que el clic no se arrastre y suelte?

Tengo este problema con los elementos ordenables, pero creo que es bueno tener una solución para arrastrar y soltar en general.

He resuelto el problema por mí mismo. Después de eso, descubrí que existe la misma solución para Scriptaculous , pero tal vez alguien tenga una mejor manera de lograrlo.

Alexander Yanovets
fuente
Similar / duplicado: stackoverflow.com/questions/3486760/…
Ryan

Respuestas:

88

Una solución que me funcionó bien y que no requiere un tiempo de espera: (sí, soy un poco pedante ;-)

Agrego una clase de marcador al elemento cuando se inicia el arrastre, por ejemplo, 'noclick'. Cuando se suelta el elemento, se activa el evento de clic; más precisamente, si el arrastre finaliza, en realidad no es necesario colocarlo en un objetivo válido. En el controlador de clic, elimino la clase de marcador si está presente; de ​​lo contrario, el clic se maneja normalmente.

$('your selector').draggable({
    start: function(event, ui) {
        $(this).addClass('noclick');
    }
});

$('your selector').click(function(event) {
    if ($(this).hasClass('noclick')) {
        $(this).removeClass('noclick');
    }
    else {
        // actual click event code
    }
});
lex82
fuente
1
¡Útil! No funcionó para mí de una vez, probablemente porque estaba jugando en intentos anteriores con lo mismo y las clases terminaron en un elemento diferente al que se hizo clic ... pero de todos modos, usé una variable global (que en este caso extremadamente página simple estaba bien), y eso también funcionó bastante bien.
MSpreij
2
Esto me llevó a mi solución, solo usé la función de datos jQuery en lugar de una clase. Gracias.
William
3
pero el navegador no activará el evento de clic cada vez que suelte un elemento
puchu
6
Para guardar la configuración de una clase o datos, en el controlador de eventos de clic también puede usar: if (! $ (This) .is ('. Ui-draggable-dragging')) {... click action}
ChrisV
1
Vaya, ¿no quieres stop: function(event, ui) { $(this).removeClass('noclick'); }en el controlador arrastrable? De lo contrario, no todos los procesos de arrastrar y soltar en otro lugar frustrarán el siguiente clic en "su selector".
Bob Stein
41

La solución es agregar un controlador de clic que evitará que el clic se propague al inicio del arrastre. Y luego elimine ese controlador después de que se realice la caída. La última acción debe retrasarse un poco para que funcione la prevención de clics.

Solución para ordenar:

...
.sortable({
...
        start: function(event, ui) {
            ui.item.bind("click.prevent",
                function(event) { event.preventDefault(); });
        },
        stop: function(event, ui) {
            setTimeout(function(){ui.item.unbind("click.prevent");}, 300);
        }
...
})

Solución para arrastrar:

...
.draggable({
...
        start: function(event, ui) {
            ui.helper.bind("click.prevent",
                function(event) { event.preventDefault(); });
        },
        stop: function(event, ui) {
            setTimeout(function(){ui.helper.unbind("click.prevent");}, 300);
        }
...
})
Alexander Yanovets
fuente
2
Parece que esto no pudo evitar el evento de clic para mí, estoy usando jquery 1.9.1 y jquery.ui 1.10.3
Aaron
1
Es extraño que hayas respondido tu propia pregunta, y ni siquiera está funcionando, Sasha lol.
@aaron, esta respuesta a continuación funciona: http://stackoverflow.com/a/4572831/119765
lol
Otra respuesta simple: simplemente coloque el controlador jQuery .click () después de .draggable () stackoverflow.com/questions/18032136/…
JxAxMxIxN
1
si pasa el asistente de atributo con el valor 'clon', evita activar el evento en el elemento arrastrado y ordenado. {helper: 'clone', start, stop ... etc},
Luchux
12

Tuve el mismo problema e intenté varios enfoques y ninguno funcionó para mí.

Solución 1

$('.item').click(function(e)
{            
    if ( $(this).is('.ui-draggable-dragging') ) return false;
});  

no hace nada por mi Se hace clic en el elemento después de que se realiza el arrastre.

Solución 2 (por Tom de Boer)

$('.item').draggable(
{   
    stop: function(event, ui) 
    {
         $( event.originalEvent.target).one('click', function(e){ e.stopImmediatePropagation(); } );
    }
});

Esto funciona bien, pero falla en un caso, cuando estaba en pantalla completa al hacer clic:

var body = $('body')[0];     
req = body.requestFullScreen || body.webkitRequestFullScreen || body.mozRequestFullScreen;
req.call(body); 

Solución 3 (por Sasha Yanovets)

 $('.item').draggable({
        start: function(event, ui) {
            ui.helper.bind("click.prevent",
                function(event) { event.preventDefault(); });
        },
        stop: function(event, ui) {
            setTimeout(function(){ui.helper.unbind("click.prevent");}, 300);
        }
})

Esto no funciona para mi.

Solución 4- el único que funcionó bien

$('.item').draggable(
{   
});
$('.item').click(function(e)
{  
});

Sí, eso es todo, el orden correcto hace el truco, primero debe vincular el evento draggable () y luego hacer clic (). Incluso cuando puse el código de alternancia de pantalla completa en el evento click (), todavía no pasaba a pantalla completa al arrastrar. ¡Perfecto para mi!

Tom
fuente
1
Solo uno que funcionó para mí (en IE11 / Edge) fue la solución 4. ¡Gracias!
Simon
1
La # 4 es la solución correcta y más simple para mí, sin usar clases ni variables.
electrotipo
11

Me gustaría agregar a esto que parece que la prevención del evento de clic solo funciona si el evento de clic se define DESPUÉS del evento que se puede arrastrar o ordenar. Si el clic se agrega primero, se activa al arrastrar.

Kasakka
fuente
9

Realmente no me gusta usar temporizadores o prevenir, así que lo que hice fue esto:

var el, dragged

el = $( '#some_element' );

el.on( 'mousedown', onMouseDown );
el.on( 'mouseup', onMouseUp );
el.draggable( { start: onStartDrag } );

onMouseDown = function( ) {
  dragged = false;
}

onMouseUp = function( ) {
  if( !dragged ) {
    console.log('no drag, normal click')
  }
}

onStartDrag = function( ) {
  dragged = true;
}

Roca sólida..

Tim Baas
fuente
Esta fue la mejor solución para mí. Tuve problemas para hacer que los eventos de clic funcionaran en una interfaz de usuario de jquery que se puede ordenar en un navegador web móvil usando jquery.ui.touch.punch.js, pero esto lo resolvió con bastante elegancia.
Godsmith
3

versión de lex82 pero para .sortable ()

 start: function(event, ui){
 ui.item.find('.ui-widget-header').addClass('noclick');
 },

y es posible que solo necesite:

 start: function(event, ui){
 ui.item.addClass('noclick');
 },

y esto es lo que estoy usando para alternar:

$("#datasign-widgets .ui-widget-header").click(function(){
if ($(this).hasClass('noclick')) {
$(this).removeClass('noclick');

}
else {
$(this).next().slideToggle();
$(this).find('.ui-icon').toggleClass("ui-icon-minusthick").toggleClass("ui-icon-plusthick");
}
});
orolo
fuente
2
Parece que jquery UI agrega la clase 'ui-sortable-helper' al elemento que se está ordenando. Por lo tanto, puede omitir la clase 'noclick' y simplemente hacer if ($ (this) .hasClass ('ui-sortable-helper')). Más conciso de esa manera
Matt De Leon
3

Una posible alternativa para la respuesta de Sasha sin evitar el incumplimiento:

var tmp_handler;
.sortable({
        start : function(event,ui){
            tmp_handler = ui.item.data("events").click[0].handler;
            ui.item.off();
        },
        stop : function(event,ui){
            setTimeout(function(){ui.item.on("click", tmp_handler)}, 300);
        },
Estilos Matt
fuente
3

En jQuery UI, los elementos que se arrastran reciben la clase "ui-draggable-dragging".
Por lo tanto, podemos usar esta clase para determinar si hacer clic o no, solo retrasar el evento.
No es necesario utilizar las funciones de devolución de llamada "iniciar" o "detener", simplemente haga lo siguiente:

$('#foo').on('mouseup', function () {
    if (! $(this).hasClass('ui-draggable-dragging')) {
        // your click function
    }
});

Esto se activa desde "mouseup", en lugar de "mousedown" o "click", por lo que hay un ligero retraso, puede que no sea perfecto, pero es más fácil que otras soluciones sugeridas aquí.

lukesrw
fuente
1

Después de leer esto y algunos hilos, esta fue la solución con la que fui.

var dragging = false;
$("#sortable").mouseover(function() {
    $(this).parent().sortable({
        start: function(event, ui) {
            dragging = true;
        },
        stop: function(event, ui) {
            // Update Code here
        }
    })
});
$("#sortable").click(function(mouseEvent){
    if (!dragging) {
        alert($(this).attr("id"));
    } else {
        dragging = false;
    }
});
jwdreger
fuente
1

En mi caso funcionó así:

$('#draggable').draggable({
  start: function(event, ui) {
    $(event.toElement).one('click', function(e) { e.stopPropagation(); });
  }
});
usuario3165434
fuente
0

¿Ha intentado deshabilitar el enlace usando event.preventDefault (); en el evento de inicio y volver a habilitarlo en el evento de arrastre detenido o en el evento de caída usando desvincular

alienonland
fuente
0

Solo una pequeña arruga para agregar a las respuestas dadas anteriormente. Tuve que hacer un div que contenga un elemento de SalesForce que se pueda arrastrar, pero el elemento de SalesForce tiene una acción onclick definida en el html a través de algún gobbledigook de VisualForce.

Obviamente, esto viola la regla "definir acción de clic después de la acción de arrastre", así que como solución, redefiní la acción del elemento SalesForce para que se active "onDblClick", y usé este código para el div contenedor:

$(this).draggable({
        zIndex: 999,
        revert: true,
        revertDuration: 0,
        start: function(event, ui) {
                   $(this).addClass('noclick');
                }
});

$(this).click(function(){
    if( $(this).hasClass('noclick'))
    {
        $(this).removeClass('noclick');
    }
    else
    {
        $(this).children(":first").trigger('dblclick');
    }
});

El evento de clic del padre esencialmente oculta la necesidad de hacer doble clic en el elemento secundario, dejando intacta la experiencia del usuario.

geekbrit
fuente
0

Intenté así:

var dragging = true; 

$(this).click(function(){
  if(!dragging){
    do str...
  }
});

$(this).draggable({
  start: function(event, ui) {
      dragging = true;
  },

  stop: function(event, ui) {
      setTimeout(function(){dragging = false;}, 300);
  }

});
Dom
fuente
0

para mí ayudó a pasar el ayudante en el objeto de opciones como:

.sortable({
   helper : 'clone', 
   start:function(), 
   stop:function(),
   .....
});

Parece que la clonación del elemento dom que se arrastra impidió el burbujeo del evento. No pude evitarlo con ningún eventPropagation, burbujeo, etc. Esta fue la única solución que me funcionó.

Luchux
fuente
0

Los eventos onmousedown y onmouseup funcionaron en uno de mis proyectos más pequeños.

var mousePos = [0,0];
function startClick()
{
    mousePos = [event.clientX,event.clientY];
}
        
function endClick()
{
    if ( event.clientX != mousePos[0] && event.clientY != mousePos[1] )
    {
        alert( "DRAG CLICK" );
    }
    else
    {
        alert( "CLICK" );
    }
}
<img src=".." onmousedown="startClick();" onmouseup="endClick();" />

Sí, lo sé. No es la forma más limpia, pero entiendes la idea.

LiquiNUX
fuente
0

la solución más sencilla y robusta? simplemente cree un elemento transparente sobre su arrastrable.

.click-passthrough {
  position: absolute;
  left: 0;
  top: 0;
  right: 0;
  bottom: 0;
  background: transparent;
}

element.draggable({        
        start: function () {

        },
        drag: function(event, ui) {
            // important! if create the 'cover' in start, then you will not see click events at all
                  if (!element.find('.click-passthrough').length) {
                      element.append("<div class='click-passthrough'></div>");
                  }
        },
        stop: function() {
          // remove the cover
          element.find('.click-passthrough').remove();
        }
    });
Andrey
fuente