Problema de pedido onclick () y onblur ()

102

Tengo un campo de entrada que abre un menú desplegable personalizado. Me gustaría la siguiente funcionalidad:

  • Cuando el usuario hace clic en cualquier lugar fuera del campo de entrada, el menú debe eliminarse.
  • Si, más específicamente, el usuario hace clic en un div dentro del menú, el menú debe eliminarse y debe producirse un procesamiento especial en función de qué div se hizo clic.

Aquí está mi implementación:

El campo de entrada tiene un onblur()evento que elimina el menú (estableciendo su padre innerHTMLen una cadena vacía) cada vez que el usuario hace clic fuera del campo de entrada. Los divs dentro del menú también tienen onclick()eventos que ejecutan el procesamiento especial.

El problema es que los onclick()eventos nunca se activan cuando se hace clic en el menú, porque el campo de entrada se onblur()activa primero y elimina el menú, incluidos los onclick()s!

Resolví el problema dividiendo los divs del menú onclick()en onmousedown()y onmouseup()eventos y estableciendo una bandera global al presionar el mouse hacia abajo que se borra al presionar el mouse hacia arriba, similar a lo que se sugirió en esta respuesta . Debido a que se onmousedown()dispara antes onblur(), la bandera se activará onblur()si se hizo clic en uno de los divs del menú, pero no si se hizo en otro lugar de la pantalla. Si se hizo clic en el menú, vuelvo inmediatamente onblur()sin eliminar el menú, luego espero a onclick()que se active, momento en el que puedo eliminar el menú de forma segura.

¿Existe una solución más elegante?

El código se parece a esto:

<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 ''
fuente

Respuestas:

168

Estaba teniendo exactamente el mismo problema que usted, mi interfaz de usuario está diseñada exactamente como lo describe. Resolví el problema simplemente reemplazando el onClickde los elementos del menú con un onMouseDown. No hice nada más; NoonMouseUp , no hay banderas. Esto resolvió el problema al permitir que el navegador reordene automáticamente según la prioridad de estos controladores de eventos, sin ningún trabajo adicional por mi parte.

¿Hay alguna razón por la que esto no hubiera funcionado para usted?

johnbakers
fuente
3
Esto realmente funciona. Lo he probado en FF y funciona de maravilla.
Supreme Dolphin
3
Funciona. Parece que onmousedownse ejecutó antes. onblurProbado en Firefox, Chrome e Internet Explorer.
Tonatio
11
Vale la pena señalar que esto cambia el comportamiento un poco de forma natural: la interacción del clic se maneja con el mouse hacia abajo en lugar de hacia arriba. Para la mayoría de las personas eso puede estar bien (incluido el yo en este caso), pero hay algunos inconvenientes. En particular, a menudo hago clic y luego arrastro el botón si hice un clic incorrecto, lo que evita que se llame a onclick; si el botón realiza una función que no es pura (eliminar, publicar, etc.), es posible que desee conservar esto y seguir el enfoque de la bandera.
Brizee
2
¿Qué pasa con la accesibilidad en el momento en que configura mouseDown en lugar de onClick, elimina la accesibilidad para todos los elementos <button>: /
ncubica
2
La respuesta que aparece a continuación por @ ian-macfarlane es la forma correcta de abordar este problema. En resumen ... onMouseDowncon event.preventDefault() y onClickcon la acción deseada.
Conal Trinitrotoluene Da Costa
47

onClick no debe ser reemplazado con onMouseDown .

Si bien este enfoque funciona de alguna manera , los dos son eventos fundamentalmente diferentes que tienen expectativas diferentes a los ojos del usuario. Usando en onMouseDownlugar deonClick arruinará la previsibilidad de su software en este caso. Por tanto, los dos eventos no son intercambiables.

Para ilustrar: al hacer clic accidentalmente en un botón, los usuarios esperan poder mantener presionado el clic del mouse, arrastrar el cursor fuera del elemento y soltar el botón del mouse, lo que finalmente no produce ninguna acción. onClickHaz esto. onMouseDownno permite que el usuario mantenga presionado el mouse y, en su lugar, activará inmediatamente una acción, sin ningún recurso para el usuario. onClickes el estándar por el cual esperamos activar acciones en una computadora.

En esta situación, llame event.preventDefault()al onMouseDownevento. onMouseDowncausará un evento de desenfoque de forma predeterminada y no lo hará cuando preventDefaultse llame. Entonces, onClicktendrá la oportunidad de ser llamado. Un evento borroso seguirá ocurriendo, solo después onClick.

Después de todo, el onClickevento es una combinación de onMouseDowny onMouseUp, si y solo si ambos ocurren dentro del mismo elemento.

Ian MacFarlane
fuente
7
Esta fue la solución perfecta para mí y el comentario es totalmente correcto: reemplazar onMouseDown es confuso para la experiencia del usuario. Han votado.
Paul Bartlett
5
esta debería ser la respuesta correcta. gracias por publicar esto
user1189352
1
Vale la pena señalar que esto también tiene algunos efectos secundarios menores, por ejemplo, no poder seleccionar texto haciendo clic dentro del elemento. No puedo pensar en muchos casos en los que esto sería un problema, solo que se siente un poco divertido.
Rei Miyasaka
1
Si lo uso event.preventDefault()en un onMouseDownevento, mi onFocusevento no se llama
Batman
4

Reemplazar onmousedowncon onfocus. Entonces, este evento se activará cuando el foco esté dentro del cuadro de texto.

Reemplazar onmouseupcon onblur. En el momento en que saque su enfoque del cuadro de texto, se ejecutará onblur.

Supongo que esto es lo que podrías necesitar.

ACTUALIZAR :

cuando ejecute su función onfocus -> elimine las clases que aplicará en onblur y agregue las clases que desea que se ejecuten onfocus

y

cuando ejecuta su función onblur -> elimine las clases que aplicará en onfocus y agregue las clases que desea que se ejecuten onblur

No veo ninguna necesidad de variables de marca.

ACTUALIZACIÓN 2:

Puede utilizar los eventos onmouseout y onmouseover

el ratón por encima -Detecta cuando el cursor está sobre él.

onmouseout -Detecta cuando el cursor sale.

HIRA THAKUR
fuente
Edité mi pregunta para mayor claridad. ¿Cómo evita esta solución la necesidad de colocar una bandera? Además, utilizo onmousedown()y onmouseup()en el menú, no el cuadro de texto / campo de entrada.
1 ''
Todavía no puedo aceptar esta respuesta porque no sé si es correcta. ¿Podría responder a las preocupaciones que planteé en mi comentario anterior?
1 ''
Claro, puedo ver cómo podría usar onmouseover y onmouseout de manera similar a lo que estoy haciendo actualmente, para establecer una bandera cuando el mouse está sobre el menú. Sin embargo, sigo pensando que necesitaría establecer una bandera en onmouseover que se borra en onmouseout, para que sepa si estaba sobre el menú cuando hizo clic. ¿Puedes pensar en una forma que no requiera poner una bandera?
1 ''
¿Puedo ver su código ... ponerlo en un violín ... solo ponga la parte relevante y está completamente bien si no veo los resultados ... solo el código ..
HIRA THAKUR
2

Una solución más elegante (pero probablemente menos eficaz):

En lugar de usar el onblur de la entrada para eliminar el menú, use document.onclick, que se dispara después onblur.

Sin embargo, esto también significa que el menú se elimina cuando se hace clic en la entrada, lo que es un comportamiento no deseado. Establezca un input.onclickcon event.stopPropagation()para evitar la propagación de clics al evento de clic del documento.

1 ''
fuente
5
Bueno, el problema con esta respuesta, sin embargo, es si no fue un evento de clic lo que terminó desencadenando el desenfoque. ¿Y si el usuario saliera de la entrada? Eso haría que se disparara el evento de desenfoque, pero el menú desplegable aún estaría visible.
Christoph
0

cambiar onclick por onfocus

incluso si onblur y onclick no se llevan muy bien, pero obviamente onfocus y sí onblur. ya que incluso después de cerrar el menú, el enfoque sigue siendo válido para el elemento en el que se hace clic dentro.

Lo hice y funcionó.

Brasil
fuente
-7

Puede usar una setIntervalfunción dentro de su onBlurcontrolador, como esta:

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

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

la setIntervalfunción eliminará su onBlur función de la pila de llamadas, agregue porque estableció el tiempo en 0, esta función se llamará inmediatamente después de que finalice otro controlador de eventos

usuario5271069
fuente
5
setIntervales una mala idea, nunca la cancelará, por lo que ejecutará continuamente su función y lo más probable es que haga que su sitio use max cpu. Úselo setTimeouten su lugar si va a usar esta técnica (que no recomiendo). Hacer que su aplicación dependa de la sincronización de ciertos eventos es un negocio arriesgado.
casey
6
¡PELIGRO SERÁ ROBINSON! ¡PELIGRO! ¡Condición de carrera por delante!
GoreDefex
Originalmente se me ocurrió esta solución (bueno, setTimeoutno setInterval) pero tuve que aumentarla a 250ms antes de que ocurriera el tiempo en el orden correcto. Este error probablemente seguirá existiendo en dispositivos más lentos y también hace que el retraso sea notable. Probé onMouseDowny funciona bien. Me pregunto si también necesita los eventos táctiles.
Brennan Cheung
Algo como un intervalo no está mal si realmente quieres usar onClick. Pero querría un tiempo de espera recursivo con una condición en lugar de un intervalo para que no se ejecute para siempre. Este es el mismo patrón utilizado por las esperas condicionales de Selenium.
Will Cain