Casos de uso prácticos para el método más cercano() de JavaScript

Índice
  1. A la antigua usanza: usando un whilebucle
  2. También está jQuery
  • La nueva forma: usarElement.closest()
    1. Caso de uso 1: menús desplegables
  • Caso de uso 2: tablas
    1. Caso de uso 3: tablas en React
    2. Caso de uso 4: modales
    3. Ah, y sobre la compatibilidad con el navegador...
  • ¿Alguna vez ha tenido el problema de encontrar el padre de un nodo DOM en JavaScript, pero no está seguro de cuántos niveles debe atravesar para llegar a él? Veamos este HTML, por ejemplo:

    div data-id="123"  buttonClick me/button/div

    Eso es bastante sencillo, ¿verdad? Supongamos que desea obtener el valor de data-iddespués de que un usuario haga clic en el botón:

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

    En este mismo caso, la API Node.parentNode es suficiente. Lo que hace es devolver el nodo padre de un elemento determinado. En el ejemplo anterior, evt.target¿se hace clic en el botón? su nodo padre es el div con el atributo de datos.

    Pero ¿qué pasa si la estructura HTML está anidada más profundamente que eso? Incluso podría ser dinámico, dependiendo de su contenido.

    div data-id="123"  article    header      h1Some title/h1      buttonClick me/button    /header     !-- ... --  /article/div

    Nuestro trabajo se volvió considerablemente más difícil al agregar algunos elementos HTML más. Claro, podríamos hacer algo como element.parentNode.parentNode.parentNode.dataset.id, pero vamos… eso no es elegante, reutilizable o escalable.

    A la antigua usanza: usando un whilebucle

    Una solución sería utilizar un whilebucle que se ejecute hasta que se encuentre el nodo principal.

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

    Usando nuevamente el mismo ejemplo HTML anterior, se vería así:

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

    Esta solución está lejos de ser perfecta. Imagínese si desea utilizar ID o clases o cualquier otro tipo de selector, en lugar del nombre de la etiqueta. Al menos permite un número variable de nodos secundarios entre el padre y nuestra fuente.

    También está jQuery

    En el pasado, si no querías escribir el tipo de función que hicimos anteriormente para cada aplicación (y seamos realistas, ¿quién quiere eso?), entonces una biblioteca como jQuery era útil (y todavía lo es). ). Ofrece un .closest()método para exactamente eso:

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

    La nueva forma: usarElement.closest()

    Aunque jQuery sigue siendo un enfoque válido (bueno, algunos de nosotros estamos en deuda con él), agregarlo a un proyecto solo para este método es excesivo, especialmente si puedes tener lo mismo con JavaScript nativo.

    Y ahí es donde Element.closestentra en acción:

    var button = document.querySelector("button");
console.log(button.closest("div"));// prints the HTMLDivElement

    ¡Aquí vamos! Así de fácil puede ser y sin bibliotecas ni código adicional.

    Element.closest()nos permite recorrer el DOM hasta que obtengamos un elemento que coincida con el selector dado. Lo genial es que podemos pasar cualquier selector al que también le daríamos Element.querySelectoro Element.querySelectorAll. Puede ser un ID, una clase, un atributo de datos, una etiqueta o lo que sea.

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

    Si Element.closestencuentra el nodo principal según el selector dado, lo devuelve de la misma manera que document.querySelector. De lo contrario, si no encuentra un padre, regresa null, lo que facilita su uso con ifcondiciones:

    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(":(");}

    ¿Listo para ver algunos ejemplos de la vida real? ¡Vamos!

    Caso de uso 1: menús desplegables

    Nuestra primera demostración es una implementación básica (y lejos de ser perfecta) de un menú desplegable que se abre después de hacer clic en uno de los elementos del menú de nivel superior. ¿Observa cómo el menú permanece abierto incluso cuando hace clic en cualquier lugar dentro del menú desplegable o selecciona texto? Pero haga clic en algún lugar del exterior y se cerrará.

    La Element.closestAPI es la que detecta ese clic externo. El menú desplegable en sí es un ulelemento con una .menu-dropdownclase, por lo que al hacer clic en cualquier lugar fuera del menú se cerrará. Esto se debe a que el valor de evt.target.closest(".menu-dropdown")será nullya que no hay ningún nodo principal con esta clase.

    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");  }}

    Dentro de la handleClickfunción de devolución de llamada, una condición decide qué hacer: cerrar el menú desplegable. Si se hace clic en algún otro lugar dentro de la lista desordenada, lo Element.closestbuscará y lo devolverá, lo que hará que el menú desplegable permanezca abierto.

    Caso de uso 2: tablas

    Este segundo ejemplo representa una tabla que muestra información del usuario, digamos como un componente en un panel. Cada usuario tiene un ID, pero en lugar de mostrarlo, lo guardamos como un atributo de datos para cada trelemento.

    table  !-- ... --  tr data-userid="1"    td      input type="checkbox" data-action="select"    /td    tdJohn Doe/td    tdjohn.doe@gmail.com/td    td      button type="button" data-action="edit"Edit/button      button type="button" data-action="delete"Delete/button    /td  /tr/table

    La última columna contiene dos botones para editar y eliminar un usuario de la tabla. El primer botón tiene el data-actionatributo de edity el segundo botón es delete. Cuando hacemos clic en cualquiera de ellos, queremos activar alguna acción (como enviar una solicitud a un servidor), pero para eso, se necesita el ID de usuario.

    Se adjunta un detector de eventos de clic al objeto de ventana global, por lo que cada vez que el usuario hace clic en algún lugar de la página, handleClickse llama a la función de devolución de llamada.

    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 se hace clic en otro lugar que no sea uno de estos botones, no data-actionexiste ningún atributo y, por lo tanto, no sucede nada. Sin embargo, al hacer clic en cualquiera de los botones, se determinará la acción (eso, por cierto, se llama delegación de eventos) y, como siguiente paso, se recuperará la identificación del usuario llamando a getUserId:

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

    Esta función espera un nodo DOM como único parámetro y, cuando se llama, lo utiliza Element.closestpara buscar la fila de la tabla que contiene el botón presionado. Luego devuelve el data-useridvalor, que ahora se puede utilizar para enviar una solicitud a un servidor.

    Caso de uso 3: tablas en React

    Sigamos con el ejemplo de la tabla y veamos cómo lo manejaríamos en un proyecto de React. Aquí está el código de un componente que devuelve una tabla:

    function TableView({ users }) {  function handleClick(evt) {    var userId = evt.currentTarget    .closest("[data-userid]")    .getAttribute("data-userid");
    // do something with `userId`  }
  return (    table      {users.map((user) = (        tr key={user.id} data-userid={user.id}          td{user.name}/td          td{user.email}/td          td            button onClick={handleClick}Edit/button          /td        /tr      ))}    /table  );}

    Encuentro que este caso de uso surge con frecuencia: es bastante común asignar un conjunto de datos y mostrarlos en una lista o tabla, y luego permitir que el usuario haga algo con ellos. Mucha gente usa funciones de flecha en línea, así:

    button onClick={() = handleClick(user.id)}Edit/button

    Si bien esta también es una forma válida de resolver el problema, prefiero utilizar la data-useridtécnica. Uno de los inconvenientes de la función de flecha en línea es que cada vez que React vuelve a representar la lista, necesita crear la función de devolución de llamada nuevamente, lo que genera un posible problema de rendimiento al manejar grandes cantidades de datos.

    En la función de devolución de llamada, simplemente manejamos el evento extrayendo el objetivo (el botón) y obteniendo el trelemento principal que contiene el data-useridvalor.

    function handleClick(evt) {  var userId = evt.target  .closest("[data-userid]")  .getAttribute("data-userid");
  // do something with `userId`}

    Caso de uso 4: modales

    Este último ejemplo es otro componente que estoy seguro que todos habéis encontrado en algún momento: un modal. Los modales suelen ser difíciles de implementar, ya que deben proporcionar muchas funciones y al mismo tiempo ser accesibles y (idealmente) atractivos.

    Queremos centrarnos en cómo cerrar el modal. En este ejemplo, eso es posible presionando Escun teclado, haciendo clic en un botón en el modal o haciendo clic en cualquier lugar fuera del modal.

    En nuestro JavaScript, queremos escuchar clics en algún lugar del modal:

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

    El modal está oculto de forma predeterminada mediante una .is-hiddenclase de utilidad. Solo cuando un usuario hace clic en el gran botón rojo se abre el modal eliminando esta clase. Y una vez que el modal está abierto, hacer clic en cualquier lugar dentro de él (con la excepción del botón de cerrar) no lo cerrará inadvertidamente. La función de devolución de llamada del detector de eventos es responsable de eso:

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

    evt.targetes el nodo DOM en el que se hace clic y que, en este ejemplo, es el fondo completo detrás del modal div. Este nodo DOM no está dentro div, por lo tanto, Element.closest()puede generar todo lo que quiera y no lo encontrará. La condición lo comprueba y activa la handleModalClosefunción.

    Al hacer clic en algún lugar dentro del nodo, digamos el encabezado, se creará divel nodo padre. En ese caso, la condición no es verdadera, dejando el modal en su estado abierto.

    Ah, y sobre la compatibilidad con el navegador...

    Al igual que con cualquier API de JavaScript “nueva” y interesante, la compatibilidad con el navegador es algo a considerar. La buena noticia es que Element.closestno es tan nuevo y es compatible con todos los principales navegadores desde hace bastante tiempo, con una enorme cobertura de soporte del 94%. Yo diría que esto se considera seguro de usar en un entorno de producción.

    El único navegador que no ofrece soporte alguno es Internet Explorer (todas las versiones). Si tiene que admitir IE, es posible que le resulte mejor utilizar el enfoque jQuery.


    Como puede ver, existen algunos casos de uso bastante sólidos para Element.closest. Lo que las bibliotecas, como jQuery, nos hicieron relativamente fácil en el pasado ahora se pueden usar de forma nativa con JavaScript básico.

    Gracias al buen soporte del navegador y a la API fácil de usar, dependo en gran medida de este pequeño método en muchas aplicaciones y todavía no me ha decepcionado.

    ¿Tiene otros casos de uso interesantes? No dudes en hacérmelo saber.

    SUSCRÍBETE A NUESTRO BOLETÍN 
    No te pierdas de nuestro contenido ni de ninguna de nuestras guías para que puedas avanzar en los juegos que más te gustan.

    Deja un comentario

    Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

    Subir

    Este sitio web utiliza cookies para mejorar tu experiencia mientras navegas por él. Este sitio web utiliza cookies para mejorar tu experiencia de usuario. Al continuar navegando, aceptas su uso. Mas informacion