Je souhaite fermer un menu déroulant lorsqu'un clic se produit en dehors du composant déroulant.
Comment je fais ça?
Je souhaite fermer un menu déroulant lorsqu'un clic se produit en dehors du composant déroulant.
Comment je fais ça?
Réponses:
Dans l'élément que j'ai ajouté mousedown
et mouseup
comme ceci:
onMouseDown={this.props.onMouseDown} onMouseUp={this.props.onMouseUp}
Ensuite, chez le parent, je fais ceci:
componentDidMount: function () {
window.addEventListener('mousedown', this.pageClick, false);
},
pageClick: function (e) {
if (this.mouseIsDownOnCalendar) {
return;
}
this.setState({
showCal: false
});
},
mouseDownHandler: function () {
this.mouseIsDownOnCalendar = true;
},
mouseUpHandler: function () {
this.mouseIsDownOnCalendar = false;
}
Le showCal
est un booléen qui true
montre dans mon cas un calendrier et le false
cache.
preventDefault()
les événements immédiatement ou le comportement natif d'Android entre en action, ce qui interfère avec le prétraitement de React. J'ai écrit npmjs.com/package/react-onclickoutside depuis.
À l'aide des méthodes de cycle de vie, ajoutez et supprimez des écouteurs d'événements dans le document.
React.createClass({
handleClick: function (e) {
if (this.getDOMNode().contains(e.target)) {
return;
}
},
componentWillMount: function () {
document.addEventListener('click', this.handleClick, false);
},
componentWillUnmount: function () {
document.removeEventListener('click', this.handleClick, false);
}
});
Consultez les lignes 48-54 de ce composant: https://github.com/i-like-robots/react-tube-tracker/blob/91dc0129a1f6077bef57ea4ad9a860be0c600e9d/app/component/tube-tracker.jsx#L48-54
Regardez la cible de l'événement, si l'événement était directement sur le composant, ou les enfants de ce composant, alors le clic était à l'intérieur. Sinon, c'était à l'extérieur.
React.createClass({
clickDocument: function(e) {
var component = React.findDOMNode(this.refs.component);
if (e.target == component || $(component).has(e.target).length) {
// Inside of the component.
} else {
// Outside of the component.
}
},
componentDidMount: function() {
$(document).bind('click', this.clickDocument);
},
componentWillUnmount: function() {
$(document).unbind('click', this.clickDocument);
},
render: function() {
return (
<div ref='component'>
...
</div>
)
}
});
Si cela doit être utilisé dans de nombreux composants, c'est mieux avec un mixin:
var ClickMixin = {
_clickDocument: function (e) {
var component = React.findDOMNode(this.refs.component);
if (e.target == component || $(component).has(e.target).length) {
this.clickInside(e);
} else {
this.clickOutside(e);
}
},
componentDidMount: function () {
$(document).bind('click', this._clickDocument);
},
componentWillUnmount: function () {
$(document).unbind('click', this._clickDocument);
},
}
Voir l'exemple ici: https://jsfiddle.net/0Lshs7mg/1/
this.refs.component
fait référence à l'élément DOM à partir de 0,14 facebook.github.io/react/blog/2015/07/03/…
Pour votre cas d'utilisation spécifique, la réponse actuellement acceptée est un peu sur-conçue. Si vous voulez écouter quand un utilisateur clique hors d'une liste déroulante, utilisez simplement un <select>
composant comme élément parent et attachez unonBlur
gestionnaire.
Le seul inconvénient de cette approche est qu'elle suppose que l'utilisateur a déjà maintenu le focus sur l'élément, et qu'elle repose sur un contrôle de formulaire (qui peut ou non être ce que vous voulez si vous prenez en compte que la tab
clé se concentre également et brouille les éléments ) - mais ces inconvénients ne sont vraiment qu'une limite pour des cas d'utilisation plus compliqués, auquel cas une solution plus compliquée pourrait être nécessaire.
var Dropdown = React.createClass({
handleBlur: function(e) {
// do something when user clicks outside of this element
},
render: function() {
return (
<select onBlur={this.handleBlur}>
...
</select>
);
}
});
onBlur
div fonctionne également avec div s'il a un tabIndex
attribut
J'ai écrit un gestionnaire d'événements générique pour les événements qui proviennent de l'extérieur du composant, react-outside-event .
La mise en œuvre elle-même est simple:
window
objet.onOutsideEvent
sur le composant cible.import React from 'react';
import ReactDOM from 'react-dom';
/**
* @param {ReactClass} Target The component that defines `onOutsideEvent` handler.
* @param {String[]} supportedEvents A list of valid DOM event names. Default: ['mousedown'].
* @return {ReactClass}
*/
export default (Target, supportedEvents = ['mousedown']) => {
return class ReactOutsideEvent extends React.Component {
componentDidMount = () => {
if (!this.refs.target.onOutsideEvent) {
throw new Error('Component does not defined "onOutsideEvent" method.');
}
supportedEvents.forEach((eventName) => {
window.addEventListener(eventName, this.handleEvent, false);
});
};
componentWillUnmount = () => {
supportedEvents.forEach((eventName) => {
window.removeEventListener(eventName, this.handleEvent, false);
});
};
handleEvent = (event) => {
let target,
targetElement,
isInside,
isOutside;
target = this.refs.target;
targetElement = ReactDOM.findDOMNode(target);
isInside = targetElement.contains(event.target) || targetElement === event.target;
isOutside = !isInside;
if (isOutside) {
target.onOutsideEvent(event);
}
};
render() {
return <Target ref='target' {... this.props} />;
}
}
};
Pour utiliser le composant, vous devez encapsuler la déclaration de classe de composant cible à l'aide du composant d'ordre supérieur et définir les événements que vous souhaitez gérer:
import React from 'react';
import ReactDOM from 'react-dom';
import ReactOutsideEvent from 'react-outside-event';
class Player extends React.Component {
onOutsideEvent = (event) => {
if (event.type === 'mousedown') {
} else if (event.type === 'mouseup') {
}
}
render () {
return <div>Hello, World!</div>;
}
}
export default ReactOutsideEvent(Player, ['mousedown', 'mouseup']);
J'ai voté pour l'une des réponses même si cela n'a pas fonctionné pour moi. Cela a fini par m'amener à cette solution. J'ai légèrement changé l'ordre des opérations. J'écoute mouseDown sur la cible et mouseUp sur la cible. Si l'un de ceux-ci retourne TRUE, nous ne fermons pas le modal. Dès qu'un clic est enregistré, n'importe où, ces deux booléens {mouseDownOnModal, mouseUpOnModal} sont remis à false.
componentDidMount() {
document.addEventListener('click', this._handlePageClick);
},
componentWillUnmount() {
document.removeEventListener('click', this._handlePageClick);
},
_handlePageClick(e) {
var wasDown = this.mouseDownOnModal;
var wasUp = this.mouseUpOnModal;
this.mouseDownOnModal = false;
this.mouseUpOnModal = false;
if (!wasDown && !wasUp)
this.close();
},
_handleMouseDown() {
this.mouseDownOnModal = true;
},
_handleMouseUp() {
this.mouseUpOnModal = true;
},
render() {
return (
<Modal onMouseDown={this._handleMouseDown} >
onMouseUp={this._handleMouseUp}
{/* other_content_here */}
</Modal>
);
}
Cela présente l'avantage que tout le code repose sur le composant enfant et non sur le parent. Cela signifie qu'il n'y a pas de code standard à copier lors de la réutilisation de ce composant.
.backdrop
)..target
) en dehors de l' .backdrop
élément et avec un index d'empilement supérieur ( z-index
).Ensuite, tout clic sur l' .backdrop
élément sera considéré "en dehors de l' .target
élément".
.click-overlay {
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
z-index: 1;
}
.target {
position: relative;
z-index: 2;
}
Vous pourriez utiliser ref
s pour y parvenir, quelque chose comme ce qui suit devrait fonctionner.
Ajoutez le ref
à votre élément:
<div ref={(element) => { this.myElement = element; }}></div>
Vous pouvez ensuite ajouter une fonction pour gérer le clic en dehors de l'élément comme ceci:
handleClickOutside(e) {
if (!this.myElement.contains(e)) {
this.setState({ myElementVisibility: false });
}
}
Puis enfin, ajoutez et supprimez les écouteurs d'événement sur le montage et le démontage.
componentWillMount() {
document.addEventListener('click', this.handleClickOutside, false); // assuming that you already did .bind(this) in constructor
}
componentWillUnmount() {
document.removeEventListener('click', this.handleClickOutside, false); // assuming that you already did .bind(this) in constructor
}
handleClickOutside
en document.addEventListener()
ajoutant une this
référence. Sinon, cela donne Uncaught ReferenceError: handleClickOutside n'est pas défini danscomponentWillMount()
Super tard à la fête, mais j'ai réussi à définir un événement de flou sur l'élément parent de la liste déroulante avec le code associé pour fermer la liste déroulante, et également à attacher un écouteur mousedown à l'élément parent qui vérifie si la liste déroulante est ouverte ou non, et arrêtera la propagation de l'événement s'il est ouvert afin que l'événement de flou ne soit pas déclenché.
Puisque l'événement mousedown bouillonne, cela empêchera tout mousedown sur les enfants de provoquer un flou sur le parent.
/* Some react component */
...
showFoo = () => this.setState({ showFoo: true });
hideFoo = () => this.setState({ showFoo: false });
clicked = e => {
if (!this.state.showFoo) {
this.showFoo();
return;
}
e.preventDefault()
e.stopPropagation()
}
render() {
return (
<div
onFocus={this.showFoo}
onBlur={this.hideFoo}
onMouseDown={this.clicked}
>
{this.state.showFoo ? <FooComponent /> : null}
</div>
)
}
...
e.preventDefault () ne devrait pas avoir à être appelé pour autant que je sache, mais Firefox ne joue pas bien sans lui pour une raison quelconque. Fonctionne sur Chrome, Firefox et Safari.
J'ai trouvé un moyen plus simple à ce sujet.
Il vous suffit d'ajouter onHide(this.closeFunction)
le modal
<Modal onHide={this.closeFunction}>
...
</Modal>
En supposant que vous ayez une fonction pour fermer le modal.
Utilisez l'excellent mixin réact-onclickoutside :
npm install --save react-onclickoutside
Puis
var Component = React.createClass({
mixins: [
require('react-onclickoutside')
],
handleClickOutside: function(evt) {
// ...handling code goes here...
}
});