Cas d’utilisation pratiques de la méthode JavaScript la plus proche ()

Online Coding Courses for Kids

Avez-vous déjà eu le problème de trouver le parent d’un nœud DOM en JavaScript, mais vous n’êtes pas sûr combien de niveaux vous devez traverser pour y accéder? Examinons ce code HTML par exemple:

 

C’est assez simple, non? Dites que vous voulez obtenir la valeur de data-id après qu’un utilisateur clique sur le bouton:

var button = document.querySelector("button");


button.addEventListener("click", (evt) => {
  console.log(evt.target.parentNode.dataset.id);
  // prints "123"
});

Dans ce cas précis, le API Node.parentNode est suffisant. Il renvoie le nœud parent d’un élément donné. Dans l’exemple ci-dessus, evt.targetest le bouton cliqué; son nœud parent est le div avec l’attribut data.

Mais que se passe-t-il si la structure HTML est imbriquée plus profondément que cela? Il pourrait même être dynamique, selon son contenu.

 
   
     

Some title

         
       

Notre travail est devenu beaucoup plus difficile en ajoutant quelques éléments HTML supplémentaires. Bien sûr, nous pourrions faire quelque chose comme element.parentNode.parentNode.parentNode.dataset.id, mais allez… qui n’est ni élégant, ni réutilisable, ni évolutif.

L’ancienne méthode: utiliser un while-boucle

Une solution consisterait à utiliser un while boucle qui s’exécute jusqu’à ce que le nœud parent ait été trouvé.

function getParentNode(el, tagName) {
  while (el && el.parentNode) {
    el = el.parentNode;
    
    if (el && el.tagName == tagName.toUpperCase()) {
      return el;
    }
  }
  
  return null;
}

En utilisant à nouveau le même exemple HTML ci-dessus, cela ressemblerait à ceci:

var button = document.querySelector("button");


console.log(getParentNode(button, 'div').dataset.id);
// prints "123"

Cette solution est loin d’être parfaite. Imaginez si vous souhaitez utiliser des identifiants ou des classes ou tout autre type de sélecteur, au lieu du nom de la balise. Au moins, cela permet un nombre variable de nœuds enfants entre le parent et notre source.

Il y a aussi jQuery

À l’époque, si vous ne vouliez pas vous occuper d’écrire le type de fonction que nous avons fait ci-dessus pour chaque application (et soyons réalistes, qui veut cela?), Alors une bibliothèque comme jQuery a été utile (et c’est toujours le cas). Il offre un .closest() méthode pour exactement cela:

$("button").closest("[data-id='123']")

La nouvelle façon: utiliser Element.closest()

Même si jQuery est toujours une approche valide (hé, certains d’entre nous y sont redevables), l’ajouter à un projet uniquement pour cette seule méthode est excessif, surtout si vous pouvez avoir la même chose avec JavaScript natif.

Et c’est là que Element.closest entre en action:

var button = document.querySelector("button");


console.log(button.closest("div"));
// prints the HTMLDivElement

On y va! C’est aussi simple que cela, et sans bibliothèque ni code supplémentaire.

Element.closest() nous permet de parcourir le DOM jusqu’à ce que nous obtenions un élément qui correspond au sélecteur donné. Ce qui est génial, c’est que nous pouvons passer n’importe quel sélecteur auquel nous donnerions également Element.querySelector ou Element.querySelectorAll. Il peut s’agir d’un ID, d’une classe, d’un attribut de données, d’une balise ou autre.

element.closest("#my-id"); // yep
element.closest(".some-class"); // yep
element.closest("[data-id]:not(article)") // hell yeah

Si Element.closest trouve le nœud parent en fonction du sélecteur donné, il le renvoie de la même manière que document.querySelector. Sinon, s’il ne trouve pas de parent, il renvoie null à la place, ce qui le rend facile à utiliser avec if conditions:

var button = document.querySelector("button");


console.log(button.closest(".i-am-in-the-dom"));
// prints HTMLElement


console.log(button.closest(".i-am-not-here"));
// prints null


if (button.closest(".i-am-in-the-dom")) {
  console.log("Hello there!");
} else {
  console.log(":(");
}

Prêt pour quelques exemples concrets? Allons-y!

Cas d’utilisation 1: listes déroulantes

Notre première démo est une implémentation basique (et loin d’être parfaite) d’un menu déroulant qui s’ouvre après avoir cliqué sur l’un des éléments de menu de niveau supérieur. Remarquez comment le menu reste ouvert même en cliquant n’importe où dans la liste déroulante ou en sélectionnant du texte? Mais cliquez quelque part à l’extérieur, et il se ferme.

le Element.closest L’API est ce qui détecte ce clic extérieur. Le menu déroulant lui-même est un

    élément avec un .menu-dropdown classe, donc cliquer n’importe où en dehors du menu le fermera. C’est parce que la valeur de evt.target.closest(".menu-dropdown") va être null car il n’y a pas de nœud parent avec cette classe.

    function handleClick(evt) {
      // ...
      
      // if a click happens somewhere outside the dropdown, close it.
      if (!evt.target.closest(".menu-dropdown")) {
        menu.classList.add("is-hidden");
        navigation.classList.remove("is-expanded");
      }
    }

    À l’intérieur de handleClick fonction de rappel, une condition décide de ce qu’il faut faire: fermer la liste déroulante. Si vous cliquez sur un autre endroit de la liste non ordonnée, Element.closest va le trouver et le retourner, ce qui fait que la liste déroulante reste ouverte.

    Cas d’utilisation 2: tableaux

    Ce deuxième exemple rend un tableau qui affiche des informations utilisateur, par exemple en tant que composant dans un tableau de bord. Chaque utilisateur a un identifiant, mais au lieu de l’afficher, nous l’enregistrons comme attribut de données pour chaque

    élément.

    
      
      
        
        
        
        
      
              John Doe[email protected]                

    La dernière colonne contient deux boutons pour modifier et supprimer un utilisateur du tableau. Le premier bouton a un data-action attribut de edit, et le deuxième bouton est delete. Lorsque nous cliquons sur l’un d’entre eux, nous voulons déclencher une action (comme envoyer une requête à un serveur), mais pour cela, l’ID utilisateur est nécessaire.

    Un écouteur d’événement de clic est attaché à l’objet de fenêtre globale, donc chaque fois que l’utilisateur clique quelque part sur la page, la fonction de rappel handleClick est appelé.

    function handleClick(evt) {
      var { action } = evt.target.dataset;
      
      if (action) {
        // `action` only exists on buttons and checkboxes in the table.
        let userId = getUserId(evt.target);
        
        if (action == "edit") {
          alert(`Edit user with ID of ${userId}`);
        } else if (action == "delete") {
          alert(`Delete user with ID of ${userId}`);
        } else if (action == "select") {
          alert(`Selected user with ID of ${userId}`);
        }
      }
    }

    Si un clic se produit ailleurs que sur l’un de ces boutons, non data-action l’attribut existe, donc rien ne se passe. Cependant, en cliquant sur l’un ou l’autre des boutons, l’action sera déterminée (cela s’appelle délégation d’événement au fait), et à l’étape suivante, l’ID utilisateur sera récupéré en appelant getUserId:

    function getUserId(target) {
      // `target` is always a button or checkbox.
      return target.closest("[data-userid]").dataset.userid;
    }

    Cette fonction attend un nœud DOM comme seul paramètre et, lorsqu’elle est appelée, utilise Element.closest pour trouver la ligne du tableau contenant le bouton enfoncé. Il renvoie ensuite le data-userid value, qui peut désormais être utilisée pour envoyer une requête à un serveur.

    Cas d’utilisation 3: tableaux dans React

    Tenons-nous en à l’exemple du tableau et voyons comment nous le gérerions sur un projet React. Voici le code d’un composant qui renvoie une table:

    function TableView({ users }) {
      function handleClick(evt) {
        var userId = evt.currentTarget
        .closest("[data-userid]")
        .getAttribute("data-userid");
    

        // do something with `userId`
      }
    

      return (
        
          {users.map((user) => (
            
              
              
              
            
          ))}
        
    {user.name}{user.email}                      
      ); }

    Je trouve que ce cas d’utilisation revient fréquemment – il est assez courant de mapper un ensemble de données et de l’afficher dans une liste ou un tableau, puis de permettre à l’utilisateur d’en faire quelque chose. De nombreuses personnes utilisent des fonctions fléchées en ligne, comme ceci:

    Bien que ce soit également un moyen valable de résoudre le problème, je préfère utiliser le data-userid technique. L’un des inconvénients de la fonction de flèche en ligne est que chaque fois que React rend à nouveau la liste, il doit créer à nouveau la fonction de rappel, ce qui peut entraîner un problème de performances lors du traitement de grandes quantités de données.

    Dans la fonction de rappel, nous traitons simplement l’événement en extrayant la cible (le bouton) et en obtenant le parent

    élément qui contient le data-userid valeur.

    function handleClick(evt) {
      var userId = evt.target
      .closest("[data-userid]")
      .getAttribute("data-userid");
    

      // do something with `userId`
    }

    Cas d’utilisation 4: Modals

    Ce dernier exemple est un autre composant que je suis sûr que vous avez tous rencontré à un moment donné: un modal. Les modaux sont souvent difficiles à mettre en œuvre car ils doivent fournir de nombreuses fonctionnalités tout en étant accessibles et (idéalement) beaux.

    Nous voulons nous concentrer sur la façon de fermer le modal. Dans cet exemple, cela est possible en appuyant sur Esc sur un clavier, en cliquant sur un bouton dans le modal, ou en cliquant n’importe où en dehors du modal.

    Dans notre JavaScript, nous voulons écouter les clics quelque part dans le modal:

    var modal = document.querySelector(".modal-outer");
    
    modal.addEventListener("click", handleModalClick);

    Le modal est masqué par défaut via un .is-hidden classe d’utilité. Ce n’est que lorsqu’un utilisateur clique sur le gros bouton rouge que le modal s’ouvre en supprimant cette classe. Et une fois le modal ouvert, cliquer n’importe où à l’intérieur – à l’exception du bouton de fermeture – ne le fermera pas par inadvertance. La fonction de rappel de l’écouteur d’événements est responsable de cela:

    function handleModalClick(evt) {
      // `evt.target` is the DOM node the user clicked on.
      if (!evt.target.closest(".modal-inner")) {
        handleModalClose();
      }
    }

    evt.target est le nœud DOM sur lequel l’utilisateur a cliqué, qui, dans cet exemple, est le fond entier derrière le modal,

Close Menu