Desidero chiudere un menu a discesa quando si verifica un clic al di fuori del componente a discesa.
Come lo faccio?
Desidero chiudere un menu a discesa quando si verifica un clic al di fuori del componente a discesa.
Come lo faccio?
Risposte:
Nell'elemento che ho aggiunto mousedown
e in mouseup
questo modo:
onMouseDown={this.props.onMouseDown} onMouseUp={this.props.onMouseUp}
Quindi nel genitore faccio questo:
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;
}
Il showCal
è un booleano che quando true
mostra nel mio caso un calendario e lo false
nasconde.
preventDefault()
immediatamente gli eventi o il comportamento Android nativo entra in gioco, con cui la preelaborazione di React interferisce. Da allora ho scritto npmjs.com/package/react-onclickoutside .
Utilizzando i metodi del ciclo di vita, aggiungere e rimuovere listener di eventi dal 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);
}
});
Controlla le righe 48-54 di questo componente: https://github.com/i-like-robots/react-tube-tracker/blob/91dc0129a1f6077bef57ea4ad9a860be0c600e9d/app/component/tube-tracker.jsx#L48-54
Guarda il target dell'evento, se l'evento era direttamente sul componente o sui figli di quel componente, il clic era all'interno. Altrimenti era fuori.
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 questo deve essere utilizzato in molti componenti, è più bello con 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);
},
}
Vedi esempio qui: https://jsfiddle.net/0Lshs7mg/1/
this.refs.component
si riferisce all'elemento DOM di 0.14 facebook.github.io/react/blog/2015/07/03/…
Per il tuo caso d'uso specifico, la risposta attualmente accettata è un po 'troppo ingegnerizzata. Se desideri ascoltare quando un utente fa clic su un elenco a discesa, utilizza semplicemente un <select>
componente come elemento genitore e allega unonBlur
gestore.
L'unico svantaggio di questo approccioèche presume che l'utente abbia già mantenuto il focus sull'elemento e si basa su un controllo del modulo (che può o meno essere quello che vuoi se si tiene conto che la tab
chiave focalizza e sfoca anche gli elementi ) - ma questi inconvenienti sono solo un limite per casi d'uso più complicati, nel qual caso potrebbe essere necessaria una soluzione più complicata.
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 funziona anche con div se ha tabIndex
attributo
Ho scritto un gestore di eventi generico per eventi che hanno origine al di fuori del componente, reazione-esterno-evento .
L'implementazione stessa è semplice:
window
all'oggetto.onOutsideEvent
sul componente di destinazione.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} />;
}
}
};
Per utilizzare il componente, è necessario racchiudere la dichiarazione della classe del componente di destinazione utilizzando il componente di ordine superiore e definire gli eventi che si desidera gestire:
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']);
Ho votato una delle risposte anche se non ha funzionato per me. Alla fine mi ha portato a questa soluzione. Ho cambiato leggermente l'ordine delle operazioni. Ascolto mouseDown sul bersaglio e mouseUp sul bersaglio. Se uno di questi restituisce TRUE, non chiudiamo il modale. Non appena viene registrato un clic, ovunque, quei due booleani {mouseDownOnModal, mouseUpOnModal} vengono reimpostati su 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>
);
}
Questo ha il vantaggio che tutto il codice rimane con il componente figlio e non con il genitore. Significa che non c'è codice boilerplate da copiare quando si riutilizza questo componente.
.backdrop
)..target
) all'esterno .backdrop
dell'elemento e con un indice di impilamento maggiore ( z-index
).Quindi qualsiasi clic .backdrop
sull'elemento verrà considerato "esterno .target
all'elemento".
.click-overlay {
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
z-index: 1;
}
.target {
position: relative;
z-index: 2;
}
Potresti usare ref
s per raggiungere questo obiettivo, qualcosa come il seguente dovrebbe funzionare.
Aggiungi ref
al tuo elemento:
<div ref={(element) => { this.myElement = element; }}></div>
È quindi possibile aggiungere una funzione per gestire il clic all'esterno dell'elemento in questo modo:
handleClickOutside(e) {
if (!this.myElement.contains(e)) {
this.setState({ myElementVisibility: false });
}
}
Infine, aggiungi e rimuovi i listener di eventi su cui verranno montati e smontati.
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
in document.addEventListener()
con l'aggiunta di this
riferimento. Altrimenti restituisce Uncaught ReferenceError: handleClickOutside non è definito incomponentWillMount()
Molto tardi per la festa, ma ho avuto successo con l'impostazione di un evento di sfocatura sull'elemento genitore del menu a discesa con il codice associato per chiudere il menu a discesa e anche allegando un listener del mouse all'elemento genitore che controlla se il menu a discesa è aperto oppure no, e interromperà la propagazione dell'evento se è aperto in modo che l'evento di sfocatura non venga attivato.
Poiché l'evento mousedown si manifesta, questo impedirà a qualsiasi mousedown sui bambini di causare una sfocatura al genitore.
/* 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 () non dovrebbe essere chiamato per quanto posso ragionare, ma firefox non funziona bene senza di esso per qualsiasi motivo. Funziona su Chrome, Firefox e Safari.
Ho trovato un modo più semplice per farlo.
Hai solo bisogno di aggiungere onHide(this.closeFunction)
il modale
<Modal onHide={this.closeFunction}>
...
</Modal>
Supponendo che tu abbia una funzione per chiudere il modal.
Usa l'eccellente mixaggio di reazioni su clic :
npm install --save react-onclickoutside
E poi
var Component = React.createClass({
mixins: [
require('react-onclickoutside')
],
handleClickOutside: function(evt) {
// ...handling code goes here...
}
});