Quero fechar um menu suspenso quando ocorre um clique fora do componente suspenso.
Como faço isso?
Quero fechar um menu suspenso quando ocorre um clique fora do componente suspenso.
Como faço isso?
Respostas:
No elemento que eu adicionei mousedowne mouseupassim:
onMouseDown={this.props.onMouseDown} onMouseUp={this.props.onMouseUp}
Então no pai eu faço isso:
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;
}
O showCalé um booleano que quando truemostra no meu caso um calendário e o falseesconde.
preventDefault()os eventos imediatamente ou o comportamento nativo do Android entra em ação, interferindo no pré-processamento do React. Escrevi npmjs.com/package/react-onclickoutside desde então.
Usando os métodos de ciclo de vida, adicione e remova ouvintes de eventos do 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);
}
});
Verifique as linhas 48-54 deste componente: https://github.com/i-like-robots/react-tube-tracker/blob/91dc0129a1f6077bef57ea4ad9a860be0c600e9d/app/component/tube-tracker.jsx#L48-54
Observe o destino do evento, se o evento estava diretamente no componente, ou nos filhos desse componente, então o clique estava dentro. Caso contrário, estava fora.
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>
)
}
});
Se for usado em muitos componentes, é melhor com um 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);
},
}
Veja o exemplo aqui: https://jsfiddle.net/0Lshs7mg/1/
this.refs.componentestá se referindo ao elemento DOM a partir de 0,14 facebook.github.io/react/blog/2015/07/03/…
Para o seu caso de uso específico, a resposta atualmente aceita é um pouco exagerada. Se você deseja ouvir quando um usuário clica em uma lista suspensa, simplesmente use um <select>componente como o elemento pai e anexe um onBlurmanipulador a ele.
A única desvantagem dessa abordagem é que ela assume que o usuário já manteve o foco no elemento e depende de um controle de formulário (que pode ou não ser o que você deseja se você levar em consideração que a tabchave também focaliza e desfoca os elementos ) - mas essas desvantagens são apenas um limite para casos de uso mais complicados, caso em que uma solução mais complicada pode ser necessária.
var Dropdown = React.createClass({
handleBlur: function(e) {
// do something when user clicks outside of this element
},
render: function() {
return (
<select onBlur={this.handleBlur}>
...
</select>
);
}
});
onBlurdiv também funciona com div se tiver tabIndexatributo
Eu escrevi um manipulador de eventos genérico para eventos que se originam fora do componente, react-outside-event .
A implementação em si é simples:
windowobjeto.onOutsideEventno 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 o componente, você precisa envolver a declaração da classe do componente de destino usando o componente de ordem superior e definir os eventos que deseja manipular:
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']);
Votei em uma das respostas, embora não tenha funcionado para mim. Isso acabou me levando a essa solução. Mudei ligeiramente a ordem das operações. Escuto mouseDown no alvo e mouseUp no alvo. Se algum deles retornar TRUE, não fechamos o modal. Assim que um clique é registrado, em qualquer lugar, esses dois booleanos {mouseDownOnModal, mouseUpOnModal} são redefinidos como falsos.
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>
);
}
Isso tem a vantagem de que todo o código fica com o componente filho, e não com o pai. Isso significa que não há código padrão para copiar ao reutilizar esse componente.
.backdrop)..target) fora do .backdropelemento e com um índice de empilhamento maior ( z-index).Então, qualquer clique no .backdropelemento será considerado "fora do .targetelemento".
.click-overlay {
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
z-index: 1;
}
.target {
position: relative;
z-index: 2;
}
Você pode usar refs para conseguir isso, algo como o seguinte deve funcionar.
Adicione o refao seu elemento:
<div ref={(element) => { this.myElement = element; }}></div>
Você pode então adicionar uma função para lidar com o clique fora do elemento, como:
handleClickOutside(e) {
if (!this.myElement.contains(e)) {
this.setState({ myElementVisibility: false });
}
}
Então, finalmente, adicione e remova os ouvintes de eventos em que serão montados e desmontados.
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
}
handleClickOutsideem document.addEventListener()adicionando thisreferência. Caso contrário, fornece Uncaught ReferenceError: handleClickOutside não está definido emcomponentWillMount()
Muito tarde para a festa, mas tive sucesso com a configuração de um evento de desfoque no elemento pai da lista suspensa com o código associado para fechar a lista suspensa e também anexando um ouvinte mousedown ao elemento pai que verifica se a lista suspensa está aberta ou não, e interromperá a propagação do evento se estiver aberto para que o evento de desfoque não seja acionado.
Uma vez que o evento de movimento do mouse borbulha, isso impedirá que qualquer movimento do mouse nas crianças cause um desfoque nos pais.
/* 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 () não deveria ser chamado tanto quanto eu posso raciocinar, mas o firefox não joga bem sem ele por qualquer motivo. Funciona no Chrome, Firefox e Safari.
Eu encontrei uma maneira mais simples de fazer isso.
Você só precisa adicionar onHide(this.closeFunction)o modal
<Modal onHide={this.closeFunction}>
...
</Modal>
Supondo que você tenha uma função para fechar o modal.
Use o excelente mixin reagir ao clicar fora :
npm install --save react-onclickoutside
E depois
var Component = React.createClass({
mixins: [
require('react-onclickoutside')
],
handleClickOutside: function(evt) {
// ...handling code goes here...
}
});