Comment puis-je obtenir l'événement click dans un innerHTML?

12

J'ai cette mise en page qui a été créée dynamiquement:

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>

Et je veux obtenir l'événement en cliquant sur chacun h4 class="name"et afficher un journal avec le numéro iassocié.

Cependant, console.logne montre que le dernier iassocié ( i=9dans ce cas), et ne fonctionne pas avec les autres inuméros. Pourquoi cela arrive-t-il? Que dois-je faire?

Luiz Adorno
la source
3
Peut-être utilisez Document.createElement () au lieu de passer la chaîne html et vous aurez beaucoup plus de facilité et je pense que vous vous retrouverez avec un meilleur code à la fin.
admcfajn
1
vous devrez à nouveau parcourir vos nœuds et attacher un événement que vous ne pouvez pas attacher à une chaîne
Eugen Sunic
Belle question, c'est un peu délicat à coup sûr, je pense qu'il pourrait être possible d'attribuer un rendu de post d'événement .. Par exemple après votre forboucle appelant une méthode pour appliquer des événements à ces éléments, mais je ne sais pas. sera jsFiddle pour tester.
Pogrindis
Création d'un gestionnaire d'événements délégué pour .card-bodyet vérifier si l'élément ( target) est une ancre avec un ID commençant par title.
Hungerstar
1
Ah, sur le point de poster une réponse. A voté pour rouvrir. Les fermetures ne sont pas nécessaires, le problème innerHTML += ...tue les écouteurs d'événements précédemment définis, pas la fermeture. Utilisez document.createElementplutôt. Voir ce fil
ggorlen

Réponses:

8

innerHTMLredessine le html complet, par conséquent tous les événements précédemment attachés sont perdus. UtilisationinsertAdjacentHTML()

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
la source
3
Wow .. Je me suis amusé avec des choses simulaires et je n'ai jamais rencontré une seule fois insertAdjacentHTML- tous les jours un jour d'école, vraiment sympa.
Pogrindis
1
Très cool et +1, mais il semble toujours coûteux et inutile d'avoir à utiliser document.getElementByIdimmédiatement après l'ajout du code HTML. Si vous ajoutez des centaines d'éléments, cela représente beaucoup de travail de traversée.
ggorlen
@ggorlen Je suis un peu d'accord sur la zone de performance par intérêt. L'utilisation de la classe nameet l'attribution de l'événement à cette classe tout en passant par l'élément (ou peut-être simplement en lisant l'événement lui-même) seraient-elles plus efficaces?
Pogrindis
1
Pas certain. Dépend de l'utilisation réelle - ma suggestion pourrait être une optimisation prématurée. Il faudrait profiler / comparer et voir. Mais si cela document.createElementpeut être utilisé, je pense que c'est généralement la meilleure approche de base. Une autre amélioration possible de ce post est d'utiliser deux boucles: une pour construire tout le HTML, puis une autre pour récupérer tous les éléments en utilisant une classe en un seul document.querySelectorAllappel et ajouter les écouteurs d'événements.
ggorlen
1
Vaut vraiment une référence, je ne vois rien de mieux à faire ce soir! :)
Pogrindis
8

L'utilisation de +=on innerHTMLdétruit les écouteurs d'événements sur les éléments à l'intérieur du HTML. La bonne façon de procéder consiste à utiliser document.createElement. Voici un exemple minimal et complet:

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

Bien sûr, le document.getElementByIdn'est pas nécessaire ici puisque nous venons de créer l'objet dans le bloc de boucle. Cela permet d'économiser des promenades dans les arbres potentiellement coûteuses.

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 vous avez des morceaux arbitraires de code HTML stratifié comme dans votre exemple, vous pouvez utiliser createElement("div"), définir sa innerHTMLfois sur le morceau et ajouter des écouteurs selon les besoins. Ajoutez ensuite les divs en tant qu'enfants de votre élément conteneur.

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
la source
1
C'est l'approche que j'envisageais, et je donnerais sans hésiter la solution, mais l'autre réponse est excellente aussi.
Pogrindis
Merci pour la réponse, mais j'ai essayé de créer la mise en page avec createElement et j'ai échoué
Luiz Adorno
1
Aucun problème. J'espère que vous réalisez que si vous avez un gros morceau de HTML, vous pouvez créer un conteneur d'élément racine comme un <div>, alors div.innerHTML += yourHugeChunkOfHTML. Il n'y a donc aucune raison que cela ne fonctionne pour aucun cas d'utilisation.
ggorlen
1
Je vais essayer de construire cette mise en page avec votre recommandation, j'ai peur d'utiliser "document.getElementById" plusieurs fois et de ralentir les performances
Luiz Adorno