Tenho elementos na página que podem ser arrastados com jQuery. Esses elementos têm evento click que navega para outra página (links comuns, por exemplo).
Qual é a melhor maneira de evitar que o clique seja disparado ao soltar esse elemento, ao mesmo tempo em que permite que o clique não seja arrastado e solte?
Tenho esse problema com elementos classificáveis, mas acho que é bom ter uma solução para arrastar e soltar em geral.
Resolvi o problema sozinho. Depois disso, descobri que a mesma solução existe para o Scriptaculous , mas talvez alguém tenha uma maneira melhor de fazer isso.
javascript
jquery
events
drag-and-drop
jquery-ui-sortable
Oleksandr Yanovets
fonte
fonte
Respostas:
Uma solução que funcionou bem para mim e que não exige tempo limite: (sim, sou um pouco pedante ;-)
Eu adiciono uma classe de marcador ao elemento quando o arrasto começa, por exemplo, 'noclick'. Quando o elemento é solto, o evento de clique é disparado - mais precisamente se o arrastar terminar, na verdade ele não precisa ser solto em um destino válido. No manipulador de cliques, removo a classe do marcador, se houver, caso contrário, o clique é processado 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 } });
fonte
stop: function(event, ui) { $(this).removeClass('noclick'); }
no manipulador arrastável? Caso contrário, nem todo arrastar e soltar em outro lugar impedirá o próximo clique em 'seu seletor'.A solução é adicionar um manipulador de cliques que evitará que o clique se propague no início do arrasto. E, em seguida, remova esse manipulador após a eliminação ser realizada. A última ação deve ser atrasada um pouco para que a prevenção de cliques funcione.
Solução para classificáveis:
... .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); } ... })
Solução para arrastar:
... .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); } ... })
fonte
Eu tive o mesmo problema e tentei várias abordagens e nenhuma funcionou para mim.
Solução 1
$('.item').click(function(e) { if ( $(this).is('.ui-draggable-dragging') ) return false; });
não faz nada por mim. O item está sendo clicado depois de arrastado.
Solução 2 (por Tom de Boer)
$('.item').draggable( { stop: function(event, ui) { $( event.originalEvent.target).one('click', function(e){ e.stopImmediatePropagation(); } ); } });
Isso funciona bem, mas falha em um caso - quando eu estava indo para tela inteira ao clicar:
var body = $('body')[0]; req = body.requestFullScreen || body.webkitRequestFullScreen || body.mozRequestFullScreen; req.call(body);
Solução 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); } })
Isso não funciona para mim.
Solução 4 - a única que funcionou bem
$('.item').draggable( { }); $('.item').click(function(e) { });
Sim, é isso - a ordem correta faz o truque - primeiro você precisa vincular o evento draggable () e depois click (). Mesmo quando coloquei o código de alternância de tela inteira no evento click (), ele ainda não foi para a tela inteira ao arrastar. Perfeito para mim!
fonte
Gostaria de acrescentar que parece que impedir o evento de clique só funciona se o evento de clique for definido DEPOIS do evento arrastável ou classificável. Se o clique for adicionado primeiro, ele será ativado ao arrastar.
fonte
Eu realmente não gosto de usar temporizadores ou prevenção, então o que fiz foi o seguinte:
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; }
Rocha sólida..
fonte
versão de lex82, mas para .sortable ()
start: function(event, ui){ ui.item.find('.ui-widget-header').addClass('noclick'); },
e você só precisa de:
start: function(event, ui){ ui.item.addClass('noclick'); },
e aqui está o que estou 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"); } });
fonte
Uma possível alternativa para a resposta de Sasha sem prevenir o inadimplemento:
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); },
fonte
Na UI jQuery, os elementos que estão sendo arrastados recebem a classe "ui-draggable-dragging".
Podemos, portanto, usar esta classe para determinar se devemos clicar ou não, apenas atrasar o evento.
Você não precisa usar as funções de retorno de chamada "iniciar" ou "parar", basta fazer:
$('#foo').on('mouseup', function () { if (! $(this).hasClass('ui-draggable-dragging')) { // your click function } });
Isso é acionado a partir de "mouseup", em vez de "mousedown" ou "clique" - portanto, há um pequeno atraso, pode não ser perfeito - mas é mais fácil do que outras soluções sugeridas aqui.
fonte
Depois de ler isso e alguns tópicos, essa foi a solução que escolhi.
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; } });
fonte
No meu caso funcionou assim:
$('#draggable').draggable({ start: function(event, ui) { $(event.toElement).one('click', function(e) { e.stopPropagation(); }); } });
fonte
Você já tentou desabilitar o link usando event.preventDefault (); no evento de início e reativando-o no evento de arrastar parado ou no evento de soltar usando desvincular?
fonte
Apenas uma pequena ruga para adicionar às respostas dadas acima. Tive que fazer um div que contenha um elemento SalesForce arrastável, mas o elemento SalesForce tem uma ação onclick definida no html por meio de algum gobbledigook VisualForce.
Obviamente, isso viola a regra "definir ação de clique após a ação de arrastar", então, como solução alternativa, redefini a ação do elemento SalesForce para ser acionada "onDblClick" e usei este código para o div do contêiner:
$(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'); } });
O evento de clique do pai essencialmente oculta a necessidade de clicar duas vezes no elemento filho, deixando a experiência do usuário intacta.
fonte
Eu tentei assim:
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); } });
fonte
para mim ajudou a passar o helper no objeto options como:
.sortable({ helper : 'clone', start:function(), stop:function(), ..... });
Parece que o elemento dom de clonagem que é arrastado impediu o borbulhamento do evento. Não consegui evitar com nenhum eventoPropagação, bubbling, etc. Essa foi a única solução que funcionou para mim.
fonte
Os eventos onmousedown e onmouseup funcionaram em um de meus projetos menores.
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();" />
Sim eu conheço. Não é a maneira mais limpa, mas essa é a ideia.
fonte
a solução mais fácil e robusta? basta criar um elemento transparente sobre o seu arrastável.
.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(); } });
fonte