Problema de pedido onclick () e onblur ()


102

Eu tenho um campo de entrada que abre um menu suspenso personalizado. Eu gostaria da seguinte funcionalidade:

  • Quando o usuário clica em qualquer lugar fora do campo de entrada, o menu deve ser removido.
  • Se, mais especificamente, o usuário clicar em um div dentro do menu, o menu deve ser removido e um processamento especial deve ocorrer com base em qual div foi clicado.

Aqui está minha implementação:

O campo de entrada tem um onblur()evento que exclui o menu (definindo o do pai innerHTMLcomo uma string vazia) sempre que o usuário clica fora do campo de entrada. Os divs dentro do menu também possuem onclick()eventos que executam o processamento especial.

O problema é que os onclick()eventos nunca disparam quando o menu é clicado, porque o campo de entrada onblur()dispara primeiro e exclui o menu, incluindo o onclick()s!

Eu resolvi o problema dividindo os divs Menu onclick()em onmousedown()e onmouseup()eventos e definir um sinalizador global no mouse para baixo, que é cancelado no mouse para cima, semelhante ao que foi sugerido em esta resposta . Como onmousedown()dispara antes onblur(), o sinalizador será definido onblur()se um dos divs do menu for clicado, mas não se em algum outro lugar da tela for clicado. Se o menu foi clicado, eu retorno imediatamente onblur()sem excluí-lo e, em seguida, aguardo o onclick()disparo, momento em que posso excluir o menu com segurança.

Existe uma solução mais elegante?

O código é parecido com este:

<div class="menu" onmousedown="setFlag()" onmouseup="doProcessing()">...</div>
<input id="input" onblur="removeMenu()" ... />

var mouseflag;

function setFlag() {
    mouseflag = true;
}

function removeMenu() {
    if (!mouseflag) {
        document.getElementById('menu').innerHTML = '';
    }
}

function doProcessing(id, name) {
    mouseflag = false;
    ...
}

Respostas:


168

Eu estava tendo exatamente o mesmo problema que você, minha IU foi projetada exatamente como você descreveu. Resolvi o problema simplesmente substituindo o onClickdos itens do menu por um onMouseDown. Eu não fiz mais nada; não onMouseUp, sem bandeiras. Isso resolveu o problema, permitindo que o navegador reordenasse automaticamente com base na prioridade desses manipuladores de eventos, sem nenhum trabalho adicional da minha parte.

Existe alguma razão pela qual isso não teria funcionado também para você?


3
Isso realmente funciona. Eu testei no FF e funciona perfeitamente.
Supreme Dolphin de

3
Funciona. Parece que onmousedownfoi executado antes onblurTestado no Firefox, Chrome e Internet Explorer.
Tonatio de

11
Vale a pena notar que isso muda o comportamento um pouco naturalmente - a interação do clique é então tratada com o mouse para baixo em vez de para cima. Para a maioria das pessoas, isso pode ser bom (inclusive neste caso), mas existem alguns inconvenientes. Mais notavelmente, frequentemente clico e arrasto o botão se tiver clicado incorretamente, o que evita que onclick seja chamado - se o botão executar uma função não pura (excluir, postar, etc.), você pode querer preservar isso e seguir a abordagem de sinalização.
Brizee

2
E a acessibilidade no momento em que você define mouseDown em vez de onClick, você elimina a acessibilidade para todos os elementos <button>: /
ncubica

2
A resposta listada abaixo por @ ian-macfarlane é a maneira correta de lidar com esse problema. Em Resumo ... onMouseDowncom event.preventDefault() e onClickcom a ação desejada.
Conal Trinitrotoluene Da Costa

47

onClicknão deve ser substituído por onMouseDown.

Embora essa abordagem funcione um pouco , os dois são eventos fundamentalmente diferentes que têm expectativas diferentes aos olhos do usuário. Usar em onMouseDownvez de onClickarruinará a previsibilidade de seu software neste caso. Portanto, os dois eventos não são intercambiáveis.

Para ilustrar: ao clicar acidentalmente em um botão, os usuários esperam conseguir manter o clique do mouse pressionado, arrastar o cursor para fora do elemento e soltar o botão do mouse, resultando em nenhuma ação. onClickfaz isso. onMouseDownnão permite que o usuário mantenha o mouse pressionado e, em vez disso, aciona imediatamente uma ação, sem nenhum recurso para o usuário. onClické o padrão pelo qual esperamos acionar ações em um computador.

Nesta situação, convoque event.preventDefault()o onMouseDownevento. onMouseDowncausará um evento de desfoque por padrão e não o fará quando preventDefaultfor chamado. Então, onClickterá a chance de ser chamado. Um evento de desfoque ainda vai acontecer, só depois onClick.

Afinal, o onClickevento é uma combinação de onMouseDowne onMouseUp, se e somente se ambos ocorrerem no mesmo elemento.


7
Esta foi a solução perfeita para mim e o comentário é totalmente correto - substituir o onMouseDown é confuso para a experiência do usuário. Já votou.
Paul Bartlett

5
esta deve ser a resposta correta. obrigado por postar isto
user1189352

1
É importante notar que isso também tem alguns efeitos colaterais menores, por exemplo, não ser capaz de selecionar o texto clicando dentro do elemento. Não consigo pensar em muitos casos em que isso seria um problema, apenas que parece um pouco engraçado.
Rei Miyasaka

1
Se eu usar event.preventDefault()no onMouseDownevento, meu onFocusevento não será chamado
Batman

4

Substitua em onmousedowncom onfocus. Portanto, este evento será acionado quando o foco estiver dentro da caixa de texto.

Substitua em onmouseupcom onblur. No momento em que você tirar o foco da caixa de texto, o onblur será executado.

Eu acho que é disso que você precisa.

ATUALIZAÇÃO :

quando você executa sua função onfocus -> remove as classes que você irá aplicar em onblur e adicione as classes que você deseja que sejam executadas onfocus

e

quando você executa sua função onblur -> remove as classes que você irá aplicar no onfocus e adicione as classes que você deseja que sejam executadas no onblur

Não vejo nenhuma necessidade de variáveis ​​de sinalização.

ATUALIZAÇÃO 2:

Você pode usar os eventos onmouseout e onmouseover

onmouseover -Detecta quando o cursor está sobre ele.

onmouseout -Detecta quando o cursor sai.


Eu editei minha pergunta para maior clareza. Como essa solução evita a necessidade de definir um sinalizador? Além disso, eu uso onmousedown()e onmouseup()no menu, não o campo de caixa de texto / entrada.
1 ''

Ainda não posso aceitar esta resposta porque não sei se está correta. Você poderia responder às preocupações que levantei em meu comentário acima?
1 ''

Claro, posso ver como você pode usar onmouseover e onmouseout de maneira semelhante ao que estou fazendo atualmente, para definir um sinalizador quando o mouse estiver sobre o menu. No entanto, ainda acho que você precisaria definir um sinalizador em onmouseover que é desmarcado em onmouseout, para que você soubesse se estava sobre o menu quando clicou. Você consegue pensar em uma maneira que não exija a definição de uma bandeira?
1 ''

Posso ver seu código ... coloque-o em um violino ... basta colocar a parte relevante e está completamente ok se eu não ver os resultados .. apenas o código ..
HIRA THAKUR


2

Uma solução mais elegante (mas provavelmente com menos desempenho):

Em vez de usar o onblur da entrada para remover o menu, use document.onclick, que dispara depois onblur.

No entanto, isso também significa que o menu é removido quando a própria entrada é clicada, o que é um comportamento indesejado. Defina um input.onclickcom event.stopPropagation()para evitar a propagação de cliques para o evento de clique do documento.


5
Bem, o problema com essa resposta, porém, é se não foi um evento de clique que acabou acionando o desfoque. E se o usuário pular para fora da entrada? Isso faria com que o evento de desfoque fosse disparado, mas o menu flutuante ainda estaria visível.
Christoph

0

mudar onclick por onfocus

mesmo que onblur e onclick não se dêem muito bem, mas obviamente onfocus e sim onblur. já que mesmo após o menu ser fechado, o onfocus é válido para o elemento clicado dentro.

Eu fiz e funcionou.


-7

Você pode usar uma setIntervalfunção dentro do seu onBlurmanipulador, como esta:

<input id="input" onblur="removeMenu()" ... />

function removeMenu() {
    setInterval(function(){
        if (!mouseflag) {
            document.getElementById('menu').innerHTML = '';
        }
    }, 0);
}

a setIntervalfunção removerá sua onBlur função da pilha de chamadas, adicione porque você definiu o tempo como 0, esta função será chamada imediatamente após a conclusão de outro manipulador de eventos


5
setIntervalé uma má ideia, você nunca o cancela, então ele executará continuamente sua função e provavelmente fará com que seu site use max cpu. Em setTimeoutvez disso, use se for usar esta técnica (que não recomendo). Fazer seu aplicativo depender do momento de certos eventos é um negócio arriscado.
casey

6
O PERIGO ROBINSON! PERIGO! Condição de corrida adiante!
GoreDefex

Eu originalmente vim com essa solução (bem, setTimeoutnão setInterval), mas tive que aumentá-la para 250ms antes que o tempo acontecesse na ordem correta. Este bug provavelmente ainda existirá em dispositivos mais lentos e também torna o atraso perceptível. Eu tentei o onMouseDowne funciona bem. Eu me pergunto se ele precisa dos eventos de toque também.
Brennan Cheung

Algo como um intervalo não é ruim se você realmente deseja usar onClick. Mas você deseja um tempo limite recursivo com uma condição, em vez de um intervalo, para que não seja executado para sempre. Este é o mesmo padrão usado por esperas condicionais de selênio.
Will Cain
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.