Problème de commande onclick () et onblur ()

105

J'ai un champ de saisie qui fait apparaître un menu déroulant personnalisé. Je souhaite la fonctionnalité suivante:

  • Lorsque l'utilisateur clique n'importe où en dehors du champ de saisie, le menu doit être supprimé.
  • Si, plus spécifiquement, l'utilisateur clique sur un div dans le menu, le menu doit être supprimé et un traitement spécial doit avoir lieu en fonction du div sur lequel l'utilisateur a cliqué.

Voici ma mise en œuvre:

Le champ de saisie a un onblur()événement qui supprime le menu (en définissant son parent innerHTMLsur une chaîne vide) chaque fois que l'utilisateur clique en dehors du champ de saisie. Les div à l'intérieur du menu ont également des onclick()événements qui exécutent le traitement spécial.

Le problème est que les onclick()événements ne se déclenchent jamais lorsque l'utilisateur clique sur le menu, car le champ de saisie se onblur()déclenche en premier et supprime le menu, y compris le onclick()s!

J'ai résolu le problème en se divisant le menu divs' onclick()en onmousedown()et des onmouseup()événements et la fixation d' un indicateur global sur le bas de la souris qui est effacée sur souris vers le haut, semblable à ce qui a été suggéré dans cette réponse . Parce que se onmousedown()déclenche avant onblur(), le drapeau sera activé onblur()si l'un des divs de menu a été cliqué, mais pas si quelque part ailleurs sur l'écran était. Si le menu a été cliqué, je reviens immédiatement de onblur()sans supprimer le menu, puis attendez que le onclick()se déclenche, à quel point je peux supprimer le menu en toute sécurité.

Existe-t-il une solution plus élégante?

Le code ressemble à ceci:

<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''
la source

Réponses:

170

J'avais exactement le même problème que vous, mon interface utilisateur est conçue exactement comme vous le décrivez. J'ai résolu le problème en remplaçant simplement onClickles éléments de menu par un fichier onMouseDown. Je n'ai rien fait d'autre; non onMouseUp, pas de drapeaux. Cela a résolu le problème en laissant le navigateur se réorganiser automatiquement en fonction de la priorité de ces gestionnaires d'événements, sans aucun travail supplémentaire de ma part.

Y a-t-il une raison pour laquelle cela n'aurait pas fonctionné aussi pour vous?

johnbakers
la source
3
Cela fonctionne réellement. Je l'ai testé en FF et ça marche comme du charme.
Supreme Dolphin
3
Ça marche. Il semble qu'il onmousedownsoit exécuté avant onblurTesté dans Firefox, Chrome et Internet Explorer.
Tonatio
11
Il convient de noter que cela modifie un peu naturellement le comportement - l'interaction de clic est alors gérée en bas de la souris plutôt qu'en haut de la souris. Pour la plupart des gens, cela peut aller (auto-inclus dans ce cas) mais il y a quelques inconvénients. Plus particulièrement, je clique souvent puis je fais glisser le bouton si j'ai mal cliqué, ce qui empêche l'appel onclick - si le bouton exécute une fonction non pure (supprimer, publier, etc.), vous voudrez peut-être le conserver et utiliser l'approche du drapeau.
Brizee
2
Qu'en est-il de l'accessibilité au moment où vous définissez mouseDown au lieu de onClick, vous tuez l'accessibilité pour tous les éléments <button>: /
ncubica
2
La réponse listée ci-dessous par @ ian-macfarlane est la bonne façon de traiter ce problème. En résumé ... onMouseDownavec event.preventDefault() et onClickavec l'action souhaitée.
Conal Trinitrotoluene Da Costa
49

onClickne doit pas être remplacé par onMouseDown.

Bien que cette approche fonctionne quelque peu , les deux sont des événements fondamentalement différents qui ont des attentes différentes aux yeux de l'utilisateur. Utiliser onMouseDownau lieu de onClickruinerait la prévisibilité de votre logiciel dans ce cas. Ainsi, les deux événements ne sont pas interchangeables.

Pour illustrer: en cliquant accidentellement sur un bouton, les utilisateurs s'attendent à pouvoir maintenir le clic de la souris enfoncé, faire glisser le curseur en dehors de l'élément et relâcher le bouton de la souris, ce qui n'entraînera finalement aucune action. onClickest ce que ca. onMouseDownne permet pas à l'utilisateur de maintenir la souris enfoncée, et à la place déclenchera immédiatement une action, sans aucun recours pour l'utilisateur. onClickest la norme par laquelle nous nous attendons à déclencher des actions sur un ordinateur.

Dans cette situation, faites appel event.preventDefault()à l' onMouseDownévénement. onMouseDownprovoquera un événement de flou par défaut, et ne le fera pas lors de l' preventDefaultappel. Ensuite, onClickaura une chance d'être appelé. Un événement de flou se produira toujours, seulement après onClick.

Après tout, l' onClickévénement est une combinaison de onMouseDownet onMouseUp, si et seulement si les deux se produisent dans le même élément.

Ian MacFarlane
la source
7
C'était la solution parfaite pour moi et le commentaire est tout à fait correct - le remplacement de onMouseDown est déroutant pour l'expérience utilisateur. Ont voté.
Paul Bartlett
5
cela devrait être la bonne réponse. merci d'avoir publié ceci
user1189352
1
Il convient de noter que cela a également des effets secondaires mineurs, par exemple ne pas pouvoir sélectionner du texte en cliquant dans l'élément. Je ne peux pas penser à trop de cas où ce serait un problème, juste que cela semble un peu drôle.
Rei Miyasaka
1
Si j'utilise event.preventDefault()sur onMouseDownévénement, mon onFocusévénement ne s'appelle pas
Batman
3

Remplacez onmousedownpar onfocus. Donc, cet événement sera déclenché lorsque le focus est à l'intérieur de la zone de texte.

Remplacez onmouseuppar onblur. Dès que vous retirez votre focus de la zone de texte, onblur s'exécutera.

Je suppose que c'est ce dont vous pourriez avoir besoin.

MISE À JOUR :

lorsque vous exécutez votre fonction onfocus -> supprimez les classes que vous allez appliquer dans onblur et ajoutez les classes que vous souhaitez exécuter sur le focus

et

lorsque vous exécutez votre fonction onblur -> supprimez les classes que vous allez appliquer dans onfocus et ajoutez les classes que vous souhaitez exécuter sur le flou

Je ne vois aucun besoin de variables d'indicateur.

MISE À JOUR 2:

Vous pouvez utiliser les événements à la souris et à la souris

onmouseover -Détecte quand le curseur est dessus.

onmouseout -Détecte le départ du curseur.

HIRA THAKUR
la source
J'ai modifié ma question pour plus de clarté. Comment cette solution évite-t-elle la nécessité de définir un indicateur? De plus, j'utilise onmousedown()et onmouseup()dans le menu, pas la zone de texte / champ de saisie.
1 ''
Je ne peux pas encore accepter cette réponse car je ne sais pas si elle est correcte. Pourriez-vous répondre aux préoccupations que j'ai soulevées dans mon commentaire ci-dessus?
1 ''
Bien sûr, je peux voir comment vous pouvez utiliser onmouseover et onmouseout de la même manière que ce que je fais actuellement, pour définir un drapeau lorsque la souris est sur le menu. Cependant, je pense toujours que vous auriez besoin de définir un indicateur dans onmouseover qui est effacé dans onmouseout, afin que vous sachiez si vous étiez au-dessus du menu lorsque vous avez cliqué. Pouvez-vous penser à un moyen qui ne nécessite pas de définir un drapeau?
1 '' du
Puis-je vous voir coder ... le mettre dans un violon ... il suffit de mettre la partie pertinente et c'est complètement ok si je ne vois pas les résultats ... juste le code ..
HIRA THAKUR
2

Une solution plus élégante (mais probablement moins performante):

Au lieu d'utiliser le onblur de l'entrée pour supprimer le menu, utilisez document.onclick, qui se déclenche après onblur.

Cependant, cela signifie également que le menu est supprimé lorsque vous cliquez sur l'entrée elle-même, ce qui est un comportement indésirable. Définissez un input.onclickavec event.stopPropagation()pour éviter de propager les clics vers l'événement de clic de document.

1''
la source
5
Eh bien, le problème avec cette réponse, cependant, est que ce n'est pas un événement de clic qui a fini par déclencher le flou. Et si l'utilisateur sortait de l'entrée? Cela provoquerait le déclenchement de l'événement de flou, mais le menu déroulant serait toujours visible.
Christoph
0

changer onclick par onfocus

même si onblur et onclick ne s'entendent pas très bien, mais évidemment onfocus et oui onblur. car même après la fermeture du menu, onfocus est toujours valide pour l'élément cliqué à l'intérieur.

Je l'ai fait et ça a marché.

Brésil
la source
-7

Vous pouvez utiliser une setIntervalfonction dans votre onBlurgestionnaire, comme ceci:

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

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

la setIntervalfonction supprimera votre onBlur fonction de la pile d'appels, ajoutez parce que vous définissez l'heure à 0, cette fonction sera appelée immédiatement après la fin d'un autre gestionnaire d'événements

user5271069
la source
5
setIntervalest une mauvaise idée, vous ne l'annulez jamais, donc cela exécutera continuellement votre fonction et provoquera probablement l'utilisation de max cpu par votre site. Utilisez à la setTimeoutplace si vous allez utiliser cette technique (que je ne recommande pas). Faire dépendre votre application du calendrier de certains événements est une entreprise risquée.
casey
6
DANGER WILL ROBINSON! DANGER! Condition de course à venir!
GoreDefex
J'ai initialement proposé cette solution (enfin setTimeoutpas setInterval), mais j'ai dû l'augmenter jusqu'à 250ms avant que le timing ne se produise dans le bon ordre. Ce bogue existera probablement toujours sur les appareils plus lents et rend le retard perceptible. J'ai essayé onMouseDownet ça marche bien. Je me demande s'il a également besoin des événements tactiles.
Brennan Cheung
Quelque chose comme un intervalle n'est pas mauvais si vous voulez vraiment utiliser onClick. Mais vous voudriez un délai d'expiration récursif avec une condition plutôt qu'un intervalle afin qu'il ne s'exécute pas indéfiniment. Il s'agit du même modèle utilisé par les attentes conditionnelles Selenium.
Will Cain