web-dev-qa-db-fra.com

React et événement flou

J'ai un problème simple avec React et la gestion des événements. Mon composant ressemble à ceci (essentiellement un tableau):

const MyList = ({ items, onBlur }) =>
<table onBlur={onBlur}}>
    <thead>
    <tr>
        <th>ID</th>
        <th>Title</th>
        <th>Publisher</th>
        <th>Year</th>
        <th>Author</th>
        <th>System</th>
        <th/>
    </tr>
    </thead>
    <tbody>
    {items.map(item => <MyListRow key={item.Id} item={item}/>)}
    </tbody>
</table>;

Je veux que l'événement blur se déclenche niquement si le focus sort du tablea. Au lieu de cela l'événement se déclenche sur chaque élément enfant de la table lorsqu'il perd le focus.

Selon les documents React laisse les événements de focus bouillonner.

La question est la suivante: comment puis-je obtenir le déclenchement de ma méthode onBlur uniquement lorsque le focus sort du tableau? IOW: Comment puis-je filtrer et éliminer les événements indésirables qui bouillonnent afin de ne révéler que les événements qui indiquent une perte de focus pour la table?

23
Peter Perot

Le problème est qu'une table n'a pas réellement de concept de focus car ce n'est pas une entrée elle-même.

Lorsque l'onBlur se déclenche sur les entrées contenues, nous vérifions le relatedTarget de l'événement onBlur qui doit être défini sur l'élément qui a le focus RECEIVED (ou null). Nous utilisons ensuite une fonction qui se déplacera vers le haut à travers parentNodes à partir de cet élément nouvellement focalisé et nous assurer que le currentTarget (la table) de notre événement n'est pas un ancêtre de l'élément nouvellement focalisé. Si la condition réussit, on suppose que la table n'a plus de focus.

const focusInCurrentTarget = ({ relatedTarget, currentTarget }) => {
  if (relatedTarget === null) return false;
  
  var node = relatedTarget.parentNode;
        
  while (node !== null) {
    if (node === currentTarget) return true;
    node = node.parentNode;
  }

  return false;
}

const onBlur = (e) => {
  if (!focusInCurrentTarget(e)) {
    console.log('table blurred');
  }
}

const MyList = ({ items, onBlur }) => (
  <table onBlur={onBlur}>
    <thead>
      <tr>
        <th>ID</th>
        <th>Title</th>
        <th>Publisher</th>
        <th>Year</th>
        <th>Author</th>
        <th>System</th>
        <th/>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td>1</td>
        <td>
          <input type="text" />
        </td>
        <td>
          <input type="text" />
        </td>
        <td>
          <input type="text" />
        </td>
        <td>
          <input type="text" />
        </td>
        <td>
          <input type="text" />
        </td>
      </tr>
    </tbody>
  </table>
);
    
ReactDOM.render(
  <MyList onBlur={onBlur} />,
  document.getElementById('root')
);
table {
  padding: 10px;
  border: 1px solid red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
<br />
<input type="text" />

Les références:

MISE À JOUR:

Suppression de l'utilisation de ReactDOM.findDOMNode

25
TheZanke

Sans fonctions personnalisées et compatible avec Internet Explorer , puisque node.contains et document.activeElement sont pris en charge depuis Internet Explorer 5, cela fonctionne:

const onBlur = (e) => { if ( !e.currentTarget.contains( document.activeElement ) ) { console.log('table blurred'); } }

  • La méthode Node.contains () renvoie une valeur booléenne indiquant si un nœud est un descendant d'un nœud donné ou non.
  • Document.activeElement renvoie l'élément actuellement focalisé, c'est-à-dire l'élément qui obtiendra les événements de frappe si l'utilisateur en tape.
5
David Riccitelli

Pour résumer, espérons-le, le pointeur utile de David Riccitelli et les informations ultérieures de Wintondeshong, cela a fonctionné pour moi:

class ActionRow extends Component {

  onBlur = (e) => {
    if ( !e.currentTarget.contains( e.relatedTarget ) ) {
      console.log('blur event');
    }
  }

  render() {
    return (
      <div onBlur={this.onBlur}>
        ..
      </div>
    )
  }
}

J'essayais de déclencher une action de sauvegarde en fonction du moment où le focus laissait une div pleine de champs de formulaire.

4
Chris Paul