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;
    ...
}
1 ''
fonte

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ê?

johnbakers
fonte
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.

Ian MacFarlane
fonte
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.

HIRA THAKUR
fonte
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.

1 ''
fonte
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.

Brasil
fonte
-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

user5271069
fonte
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