Como posso obter o evento click dentro de um innerHTML?

12

Eu tenho esse layout que foi criado dinamicamente:

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>

E quero obter o evento, clique em cada um h4 class="name"e mostre um log com o número irelacionado.

No entanto, console.logmostra apenas o último irelacionado ( i=9neste caso) e não funciona com os outros inúmeros. Por que isso acontece? O que eu tenho que fazer?

Luiz Adorno
fonte
3
Talvez use Document.createElement () em vez de passar html-string e você terá um tempo muito mais fácil e acho que você acabará com um código melhor no final.
admcfajn 14/02
11
você terá que percorrer seus nós novamente e anexar um evento que não pode ser anexado a uma string
Eugen Sunic
Boa pergunta, é meio complicado, com certeza, acho que pode ser possível assistir a uma renderização pós-evento. Por exemplo, após seu forloop chamar um método para aplicar eventos a esses elementos, mas eu não sei. jsFiddle para testar.
Pogrindis 14/02
Criou um manipulador de eventos delegados para .card-bodye verifique se o elemento ( target) é uma âncora e um ID que começam com title.
hungerstar
11
Ah, prestes a postar uma resposta. Votou para reabrir. Os fechamentos não são necessários, o problema está innerHTML += ...matando os ouvintes de eventos definidos anteriormente, não o encerramento. Use em document.createElementvez disso. Veja este tópico
ggorlen 14/02

Respostas:

8

innerHTMLredesenha o html completo, como resultado, todos os eventos anexados anteriormente são perdidos. UsarinsertAdjacentHTML()

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
fonte
3
Uau .. Eu andei brincando com coisas semelhantes e nunca vi uma vez insertAdjacentHTML- todos os dias um dia de escola, muito bom.
Pogrindis 14/02
11
Muito legal e +1, mas ainda parece caro e desnecessário ter que usar document.getElementByIdimediatamente após adicionar o HTML. Se você estiver adicionando centenas de elementos, isso é muito trabalhoso.
ggorlen 14/02
@ggorlen Eu meio que concordo que a área de desempenho fora de interesse usaria a classe namee participaria do evento apenas nessa classe enquanto passava pelo elemento (ou talvez apenas lendo o próprio evento) seria mais eficiente?
Pogrindis 14/02
11
Não tenho certeza. Depende do uso real - minha sugestão pode ser a otimização prematura. Você teria que criar um perfil / benchmark e ver. Mas se document.createElementpuder ser usado, acho que geralmente é a melhor abordagem de linha de base. Outra possível melhoria para este post é usar dois loops: um para criar todo o HTML e outro para pegar todos os elementos usando uma classe em uma document.querySelectorAllchamada e adicionar os ouvintes do evento.
ggorlen 14/02
11
Definitivamente vale uma referência, não consigo pensar em nada melhor para fazer hoje à noite! :)
Pogrindis 14/02
8

Usar +=on innerHTMLdestrói ouvintes de eventos nos elementos dentro do HTML. A maneira correta de fazer isso é usar document.createElement. Aqui está um exemplo completo e mínimo:

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

Obviamente, isso document.getElementByIdnão é necessário aqui, pois acabamos de criar o objeto no bloco de loop. Isso economiza caminhadas em árvores 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;
}

Se você tiver blocos arbitrários de HTML com strings, como no seu exemplo, poderá usá-los createElement("div"), defina innerHTMLuma vez para o bloco e adicione ouvintes conforme necessário. Em seguida, acrescente os divs como filhos do seu elemento container.

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
fonte
11
Esta é a abordagem que eu estava considerando, e eu daria a solução, mas a outra resposta também é ótima.
Pogrindis 14/02
Obrigado pela resposta, mas tentei criar o layout com createElement e não tive sucesso.
Luiz Adorno
11
Sem problemas. Espero que você perceba que, se você possui um grande pedaço de HTML, pode criar um contêiner de elemento raiz como um <div>, então div.innerHTML += yourHugeChunkOfHTML. Portanto, não há razão para que isso não funcione em nenhum caso de uso.
ggorlen 14/02
11
Vou tentar criar esse layout com sua recomendação, tenho medo de usar "document.getElementById" várias vezes e diminuir o desempenho
Luiz Adorno