Evitar múltiples toggle con misma clase – javascript

Pregunta:


Para un evento toggle con puro javascript lo desarrollo de la siguiente forma:

Javascript

 // Esta clase lo expande.
const expandButton = document.querySelector('.expand-button');
expandButton.addEventListener('click', ()=> {
    //Esta es la clase que muestra el contenido.
    const more = document.querySelector('.more');
   //El contenido pasa de oculto a ser visible.
   more.style.display = 'block';
  //Oculto la imagen al expandir.
   if (more.style.display === 'block') {
       expandButton.style.display = 'none';
   }
}, false);

HTML

<div class="more"> Contenido 1 </div> <!-- El contenido -->
<div class="expand-button">Expandir</div> <!-- Botón que expande -->

El problema está en que si tengo otro toogle con la misma clase

<div class="more"> Contenido 2 </div> <!-- El contenido -->
<div class="expand-button">Expandir</div> <!-- Botón que expande -->

Debería de recuperar los elementos con la clase expand-button con un for(), cuando lo hago, el botón expandir del segundo toggle abre al primero, y el primero realiza su función correctamente.

¿Alguna solución?

Preguntado por: UnderZero_-

Alvaro Montoro

El problema ocurre por esta línea de código:

const more = document.querySelector('.more');

Como haces la selección con querySelector y hay varios elementos con la clase “more”, entonces el navegador se queda sólo con el primero, pulses en el botón de expandir que pulses.

Una posible solución sería, si puedes, añadir un data-atributo al botón de expandir que le indique cuál de los .more debe abrirse (y añadirle un id a cada uno de los contenedores). Algo como esto:

<div id="contenido1" class="more"> Contenido 1 </div>
<div class="expand-button" data-toggle="#contenido1">Expandir</div>

<div id="contenido2" class="more"> Contenido 2 </div>
<div class="expand-button" data-toggle="#contenido2">Expandir</div>

Entonces el JavaScript cambiaría para seleccionar no el “.more” sino el id especificado en el data-toggle. Aquí dejo un ejemplo funcionando:

const expandButton = document.querySelectorAll('.expand-button');

expandButton.forEach(function(el, arr) {
    el.addEventListener('click', (e)=> {
        //Esta es la clase que muestra el contenido.
        var more = document.querySelector(e.target.dataset.toggle);
        //El contenido pasa de oculto a ser visible.
        more.style.display = 'block';
        //Oculto la imagen al expandir.
        if (more.style.display === 'block') {
            el.style.display = 'none';
        }
    }, false);
});
.more { display:none }
<div id="contenido1" class="more"> Contenido 1 </div>
<div class="expand-button" data-toggle="#contenido1">Expandir</div>

<div id="contenido2" class="more"> Contenido 2 </div>
<div class="expand-button" data-toggle="#contenido2">Expandir</div>

Actualización: como se menciona en los comentarios, el código presenta problemas en algunas versions de Internet Explorer. Eso ocurre porque se usan un par de cosas (forEach y la notación => para funciones) que no están bien soportadas en versiones “antiguas” de IE.

Estos problemas se pueden solucionar de manera simple:

  • Cambiando el forEach por un bucle for simple (y las referencias a el por referencias al elemento dentro del array que se atraviesa)
  • Cambiando (e) => { ... } por el tradicional function(e) { ... }

Con esos dos cambios, el código ya funciona en Chrome, Firefox e Internet Explorer:

const expandButton = document.querySelectorAll('.expand-button');

for (var x = 0; x < expandButton.length; x++) {
    expandButton[x].addEventListener('click', function(e) {
        //Esta es la clase que muestra el contenido.
        var more = document.querySelector(e.target.dataset.toggle);
        //El contenido pasa de oculto a ser visible.
        more.style.display = 'block';
        //Oculto la imagen al expandir.
        if (more.style.display === 'block') {
            e.target.style.display = 'none';
        }
    }, false);
}
.more { display:none }
<div id="contenido1" class="more"> Contenido 1 </div>
<div class="expand-button" data-toggle="#contenido1">Expandir</div>

<div id="contenido2" class="more"> Contenido 2 </div>
<div class="expand-button" data-toggle="#contenido2">Expandir</div>

Puedes navegar el DOM desde el botón hacia el hermano anterior, usando la propiedad previousSibling.

De esta manera obtienes el <div> anterior en la estructura y expandes unicamente ese.

Algo así: (fíjate que uso this.previousSibling lo cual selecciona únicamente el hermano anterior del botón sobre el que haces click)

const expandButton = document.querySelectorAll('.expand-button');
expandButton.addEventListener('click', ()=> {

   const more = this.previousSibling; // aqui se usaria

   //El contenido pasa de oculto a ser visible.
   more.style.display = 'block';
  //Oculto la imagen al expandir.
   if (more.style.display === 'block') {
       expandButton.style.display = 'none';
   }
}, false);

Ten en cuenta que esto funciona con la estructura HTML que pones en la pregunta, si cambias la estructura, sera necesario modifacar el codigo para navegar al elemento correcto.

Te recomiendo que leas sobre otras propiedades similares del objeto Node tales como nextSibling, parentNode, etc.

MAS..

Me acabo de dar cuenta que la forma en que estableces el listener no funcionara para todos los botones con la clase .expand-button. Esto se debe a que querySelector toma únicamente el primer elemento que cumpla con la query.

Para asignar el handler a todos los botones deberas usar querySelectorAll y luego iterar sobre el resultado para asignar el manejador a todos los elementos.

Para ES2015:

var expandButtons = document.querySelectorAll('.expand-button');

Array.from(expandButtons).forEach(button => {
    button.addEventListener('click', function(event) {

        const more = this.previousSibling; 
        /// resto del código aquí.

    });
});

La Respuesta que ofreció Alvaro Montoro es correcta, pero tiene un handicap, IE no soporta forEach(), así que siguiendo la linea que nos muestra Alvaro tuve que cambiar el forEach por un for, de esta forma el toggle funciona también en IE.

Espero que os sirva de ayuda como me lo ha sido a mi.

const expandButton = document.querySelectorAll('.expand-button');
for (let i = 0; i < expandButton.length; i++) {
    var element = expandButton[i];

    element.addEventListener('click', (e) => {
        var more = document.querySelector(e.target.dataset.toggle);
        more.style.display = 'block';   
    }, false);
}
.more { display:none }
<div id="contenido1" class="more"> Contenido 1 </div>
<div class="expand-button" data-toggle="#contenido1">Expandir</div>

<div id="contenido2" class="more"> Contenido 2 </div>
<div class="expand-button" data-toggle="#contenido2">Expandir</div>

Fuente

Add a Comment

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