¿Cómo puedo obtener el evento click dentro de innerHTML?

12

Tengo este diseño que se creó dinámicamente:

for (let i = 1; i < 10; i++) {
  document.querySelector('.card-body').innerHTML += `<div class="row" id="img_div">
        <div class="col-12 col-sm-12 col-md-2 text-center">
          <img src="http://placehold.it/120x80" alt="prewiew" width="120" height="80">
        </div>
        <div id="text_div" class="col-12 text-sm-center col-sm-12 text-md-left col-md-6">
        <h4 class="name"><a href="#" id="title` + i + `">Name</a></h4>
          <h4>
            <small>state</small>
          </h4>
          <h4>
            <small>city</small>
          </h4>
          <h4>
            <small>zip</small>
          </h4>
        </div>
        <div class="col-12 col-sm-12 text-sm-center col-md-4 text-md-right row">
        </div>
      </div>
     `
  document.getElementById("title" + i).addEventListener('click', function() {
    console.log(i)
  });
}
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<div class="card-body">
  <!-- person -->
</div>

Y quiero obtener el evento, haga clic en cada uno h4 class="name"y muestre un registro con el número irelacionado.

Sin embargo, console.logmuestra solo el último irelacionado ( i=9en este caso) y no funciona con los otros inúmeros. ¿Por qué pasó esto? ¿Que tengo que hacer?

Luiz Adorno
fuente
3
Tal vez use Document.createElement () en lugar de pasar html-string y tendrá un tiempo mucho más fácil y creo que terminará con un mejor código al final.
admcfajn
1
tendrás que recorrer tus nodos nuevamente y adjuntar un evento que no puedes adjuntar a una cadena
Eugen Sunic
Buena pregunta, es un poco complicado, creo que podría ser posible adjuntar un evento después de la representación. Por ejemplo, después de su forbucle llamando a un método para aplicar eventos a esos elementos, pero no lo sé. jsFiddle para probar.
Pogrindis
Creó un controlador de eventos delegado para .card-bodyy verifica si el elemento ( target) es un ancla con una ID que comienza con title.
hungerstar
1
Ah, a punto de publicar una respuesta. Votado para volver a abrir. Los cierres no son necesarios, el problema es innerHTML += ...matar a los oyentes de eventos previamente establecidos, no el cierre. Usar en su document.createElementlugar. Ver este hilo
ggorlen

Respuestas:

8

innerHTMLvuelve a dibujar el html completo, como resultado se pierden todos los eventos adjuntos previamente. UtilizarinsertAdjacentHTML()

for(let i=1;i<10;i++){
    document.querySelector('.card-body').insertAdjacentHTML('beforeend',`<div class="row" id="img_div">
        <div class="col-12 col-sm-12 col-md-2 text-center">
          <img src="http://placehold.it/120x80" alt="prewiew" width="120" height="80">
        </div>
        <div id="text_div" class="col-12 text-sm-center col-sm-12 text-md-left col-md-6">
        <h4 class="name"><a href="#" id="title`+i+`">Name</a></h4>
          <h4>
            <small>state</small>
          </h4>
          <h4>
            <small>city</small>
          </h4>
          <h4>
            <small>zip</small>
          </h4>
        </div>
        <div class="col-12 col-sm-12 text-sm-center col-md-4 text-md-right row">
        </div>
      </div>
     `);
   document.getElementById("title"+i).addEventListener('click', function () {
    console.log(i)
  });
}
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
<div class="card-body">
<!-- person -->
</div>

Mamun
fuente
3
Wow ... He estado jugando con cosas similares y nunca me he cruzado una vez insertAdjacentHTML, todos los días de escuela, muy agradable.
Pogrindis
1
Muy bueno y +1, pero aún parece costoso e innecesario tener que usar document.getElementByIdinmediatamente después de agregar el HTML. Si está agregando cientos de elementos, esto es mucho trabajo de desplazamiento.
ggorlen
@ggorlen Estoy un poco de acuerdo en que el área de namerendimiento fuera de interés sería usar la clase y asociar el evento solo a esa clase mientras pasa por el elemento (o tal vez solo leer el evento en sí) ¿sería más eficiente?
Pogrindis
1
No estoy seguro. Depende del uso real: mi sugerencia podría ser la optimización prematura. Tendrías que hacer un perfil / punto de referencia y ver. Pero si document.createElementse puede usar, creo que ese es generalmente el mejor enfoque de referencia. Otra posible mejora de esta publicación es usar dos bucles: uno para construir todo el HTML, luego otro para tomar todos los elementos usando una clase en una document.querySelectorAllllamada y agregar los oyentes de eventos.
ggorlen
1
Definitivamente vale la pena un punto de referencia, ¡no se me ocurre nada mejor que hacer esta noche! :)
Pogrindis
8

Usar +=on innerHTMLdestruye los oyentes de eventos en los elementos dentro del HTML. La forma correcta de hacer esto es usar document.createElement. Aquí hay un ejemplo mínimo y completo:

for (let i = 1; i < 10; i++) {
  const anchor = document.createElement("a");
  document.body.appendChild(anchor);
  anchor.id = "title" + i;
  anchor.href= "#";
  anchor.textContent = "link " + i;
  document.getElementById("title" + i)
    .addEventListener("click", e => console.log(i));
}
a {
  margin-left: 0.3em;
}

Por supuesto, document.getElementByIdaquí no es necesario ya que acabamos de crear el objeto en el bloque de bucle. Esto ahorra caminatas arbóreas potencialmente caras.

for (let i = 1; i < 10; i++) {
  const anchor = document.createElement("a");
  document.body.appendChild(anchor);    
  anchor.href= "#";
  anchor.textContent = "link " + i;
  anchor.addEventListener("click", e => console.log(i));
}
a {
  margin-left: 0.3em;
}

Si tiene fragmentos arbitrarios de HTML en cadena como en su ejemplo, puede usarlo createElement("div"), establecerlo innerHTMLuna vez en el fragmento y agregar oyentes según sea necesario. Luego agregue los divs como elementos secundarios de su elemento contenedor.

for (let i = 1; i < 10; i++) {
  const rawHTML = `<div><a href="#" id="link-${i}">link ${i}</a></div>`;
  const div = document.createElement("div");
  document.body.appendChild(div);
  div.href= "#";
  div.innerHTML = rawHTML;
  div.querySelector(`#link-${i}`)
    .addEventListener("click", e => console.log(i));
}

ggorlen
fuente
1
Este es el enfoque que estaba considerando, y sin duda le daría la solución, pero la otra respuesta también es excelente.
Pogrindis
Gracias por la respuesta, pero intenté construir el diseño con createElement y no tuve éxito
Luiz Adorno
1
No hay problema. Espero que se dé cuenta de que si tiene una gran cantidad de HTML, puede crear un contenedor de elementos raíz como a <div>, entonces div.innerHTML += yourHugeChunkOfHTML. Así que no hay razón para que esto no funcione para ningún caso de uso.
ggorlen
1
Intentaré crear este diseño con su recomendación, me temo que usaré "document.getElementById" varias veces y reduciré el rendimiento
Luiz Adorno