En esta lección vamos a resolver un problema típico de frontend utilizando los conocimientos que hemos adquirido anteriormente.
Imagínate un menú normal en el que al hacer clic en un elemento, este se vuelve activo:
See the Pen js_dom_events_in_action_nav by Códica (@hexlet) on CodePen.
El principio de funcionamiento es el siguiente:
- El usuario hace clic en un enlace
- Se le agrega la clase
active - Esto resalta el nuevo elemento del menú
Con el elemento previamente seleccionado, ocurre exactamente lo contrario: se elimina la clase active del enlace.
Si resolvemos este problema de manera directa, necesitaremos extraer todos los enlaces del menú del DOM y agregar un controlador de clic a cada uno de ellos.
Este controlador activará el elemento actual y desactivará el anterior:
// Extraemos todos los enlaces
const links = document.querySelectorAll('a');
// Agregamos un evento a cada botón
// Recorremos todos los enlaces y agregamos un controlador a cada uno
links.forEach((link) => {
link.addEventListener('click', () => {
// Desactivamos el elemento previamente seleccionado
// el algoritmo de desactivación se explicará más adelante
// Resaltamos el actual
link.classList.add('active');
});
});
¿Cómo se puede desactivar correctamente el elemento anteriormente seleccionado? El problema es que no sabemos qué elemento está seleccionado. Esta tarea tiene varias soluciones.
Por ejemplo, se puede recordar el elemento seleccionado y quitarle la clase necesaria cuando se hace clic en un nuevo elemento. Este enfoque requiere la introducción de un estado, es decir, una variable que almacene el elemento seleccionado actualmente. La carga inicial agrega complejidad adicional. Esto ocurre porque el elemento seleccionado generalmente se encuentra en el HTML ya preparado, es decir, JavaScript no tiene información sobre qué elemento está activo. En general, este enfoque es bastante complicado de implementar.
Es mucho más fácil seguir otro camino. En lugar de actualizar los elementos individualmente, simplemente quitaremos la selección de todos los elementos a la vez. No importa si un elemento está seleccionado o no. La operación de eliminación de una clase es idempotente, es decir, no produce un error si intentamos eliminar una clase que no existe en el elemento:
links.forEach((link) => {
link.addEventListener('click', () => {
// Eliminamos la clase activa de todos los enlaces
links.forEach((link) => link.classList.remove('active'));
link.classList.add('active');
});
});
Esta solución es mucho más sencilla, aunque realiza un poco de trabajo adicional. Podríamos haber terminado aquí si no fuera por un obstáculo importante. La inicialización del menú no funciona correctamente. ¿Por qué?
El problema es que no todos los menús son controlados por JavaScript, pero el código actual no lo tiene en cuenta. Intentará trabajar con cualquier menú en la página, lo cual es incorrecto.
Es mejor utilizar un indicador especial que muestre que el menú debe ser controlado con JavaScript. Es considerada una buena práctica utilizar el atributo data-* para esto.
En otras palabras, la lógica de JavaScript solo se agregará a los menús que tengan el atributo necesario:
<ul class="nav">
<li>
<a class="active" data-toggle="tab" href="#home">Home</a>
</li>
<li>
<a data-toggle="tab" href="#profile">Profile</a>
</li>
<li>
<a data-toggle="tab" href="#contact">Contact</a>
</li>
</ul>
En consecuencia, nuestro código debe cambiar a lo siguiente:
const links = document.querySelectorAll('[data-toggle="tab"]');
Pero incluso en este caso, el código no funciona correctamente. Ahora está construido en base a la suposición de que solo hay un menú en la página. Sin embargo, el componente nav puede agregarse a la página un número ilimitado de veces. Por ejemplo, en Códica hay lugares con tres menús en una sola página.
Si ejecutas el código actual con varios menús, puedes notar el siguiente error. Al hacer clic en cualquier elemento de un menú, se desactiva la selección de otro menú. Esto ocurre debido a esta línea:
// links se refiere a todos los enlaces de todos los menús en la página
links.forEach((link) => link.classList.remove('active'));
Para resolver este problema, solo debemos actualizar los enlaces que pertenecen al menú actual. Pero, ¿cómo sabemos cuáles pertenecen y cuáles no? Para hacer esto, debemos realizar dos acciones:
- Encontrar el elemento raíz del menú al que pertenece el elemento en el que se hizo clic.
- Luego, encontrar el enlace activo dentro del menú actual y quitarle la selección.
links.forEach((link) => {
link.addEventListener('click', () => {
// closest encuentra el elemento padre más cercano que coincide con el selector especificado
// Nuestro menú tiene la clase .nav
const nav = link.closest('.nav');
// Encontramos el elemento activo dentro del menú
const activeElement = nav.querySelector('.active');
activeElement.classList.remove('active');
link.classList.add('active');
});
});
Para acceder completo a curso necesitas un plan básico
El plan básico te dará acceso completo a todos los cursos, ejercicios y lecciones de Códica, proyectos y acceso de por vida a la teoría de las lecciones completadas. La suscripción se puede cancelar en cualquier momento.