Quiero cerrar un menú desplegable cuando se produce un clic fuera del componente desplegable.
¿Cómo puedo hacer eso?
Quiero cerrar un menú desplegable cuando se produce un clic fuera del componente desplegable.
¿Cómo puedo hacer eso?
Respuestas:
En el elemento que he agregado mousedown
y mouseup
así:
onMouseDown={this.props.onMouseDown} onMouseUp={this.props.onMouseUp}
Luego, en el padre hago esto:
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;
}
El showCal
es un booleano que cuando true
muestra en mi caso un calendario y lo false
oculta.
preventDefault()
a los eventos de inmediato o se activa el comportamiento nativo de Android, con lo que el preprocesamiento de React interfiere. Escribí npmjs.com/package/react-onclickoutside desde entonces.
Usando los métodos de ciclo de vida, agregue y elimine detectores de eventos al documento.
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);
}
});
Consulte las líneas 48-54 de este componente: https://github.com/i-like-robots/react-tube-tracker/blob/91dc0129a1f6077bef57ea4ad9a860be0c600e9d/app/component/tube-tracker.jsx#L48-54
Mire el destino del evento, si el evento estaba directamente en el componente o en los hijos de ese componente, entonces el clic estaba dentro. De lo contrario, estaba afuera.
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 se va a utilizar en muchos componentes, es mejor con una mezcla:
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);
},
}
Vea el ejemplo aquí: https://jsfiddle.net/0Lshs7mg/1/
this.refs.component
se refiere al elemento DOM a partir de 0.14 facebook.github.io/react/blog/2015/07/03/…
Para su caso de uso específico, la respuesta actualmente aceptada es un poco sobre-diseñada. Si desea escuchar cuando un usuario hace clic fuera de una lista desplegable, simplemente use un <select>
componente como elemento principal y adjunte un onBlur
controlador.
Los únicos inconvenientes de este enfoque es que asume que el usuario ya ha mantenido el foco en el elemento, y se basa en un control de formulario (que puede ser o no lo que desea si tiene en cuenta que la tab
clave también enfoca y desenfoca elementos ) - pero estos inconvenientes son solo un límite para casos de uso más complicados, en cuyo caso podría ser necesaria una solución más complicada.
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 también funciona con div si tiene tabIndex
atributo
He escrito un controlador de eventos genérico para eventos que se originan fuera del componente, react-outside-event .
La implementación en sí es simple:
window
objeto.onOutsideEvent
en el componente de destino.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} />;
}
}
};
Para usar el componente, necesita ajustar la declaración de clase del componente de destino usando el componente de orden superior y definir los eventos que desea manejar:
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']);
Voté una de las respuestas a pesar de que no funcionó para mí. Terminó llevándome a esta solución. Cambié ligeramente el orden de las operaciones. Escucho mouseDown en el objetivo y mouseUp en el objetivo. Si alguno de ellos devuelve TRUE, no cerramos el modal. Tan pronto como se registra un clic, en cualquier lugar, esos dos valores booleanos {mouseDownOnModal, mouseUpOnModal} se vuelven a establecer en falso.
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>
);
}
Esto tiene la ventaja de que todo el código recae en el componente hijo y no en el padre. Significa que no hay un código repetitivo para copiar al reutilizar este componente.
.backdrop
)..target
) fuera del .backdrop
elemento y con un índice de apilamiento mayor ( z-index
).Entonces, cualquier clic en el .backdrop
elemento se considerará "fuera del .target
elemento".
.click-overlay {
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
z-index: 1;
}
.target {
position: relative;
z-index: 2;
}
Puede usar ref
s para lograr esto, algo como lo siguiente debería funcionar.
Agregue el ref
a su elemento:
<div ref={(element) => { this.myElement = element; }}></div>
Luego puede agregar una función para manejar el clic fuera del elemento de esta manera:
handleClickOutside(e) {
if (!this.myElement.contains(e)) {
this.setState({ myElementVisibility: false });
}
}
Luego, finalmente, agregue y elimine los detectores de eventos que se montarán y desmontarán.
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()
añadiendo this
referencia. De lo contrario, da un error de referencia no detectado: handleClickOutside no está definido encomponentWillMount()
Súper tarde para la fiesta, pero tuve éxito al configurar un evento de desenfoque en el elemento principal del menú desplegable con el código asociado para cerrar el menú desplegable, y también adjuntar un oyente del mousedown al elemento principal que verifica si el menú desplegable está abierto o no, y detendrá la propagación del evento si está abierto para que no se active el evento de desenfoque.
Dado que el evento mousedown aparece, esto evitará que cualquier mousedown en los niños cause un desenfoque en el padre.
/* 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 () no debería tener que ser llamado hasta donde puedo razonar, pero firefox no funciona bien sin él por cualquier razón. Funciona en Chrome, Firefox y Safari.
Encontré una forma más sencilla de hacerlo.
Solo necesitas agregar onHide(this.closeFunction)
el modal
<Modal onHide={this.closeFunction}>
...
</Modal>
Suponiendo que tiene una función para cerrar el modal.
Utilice el excelente mixin react-onclickoutside :
npm install --save react-onclickoutside
Y entonces
var Component = React.createClass({
mixins: [
require('react-onclickoutside')
],
handleClickOutside: function(evt) {
// ...handling code goes here...
}
});