Wie kann ich das Klickereignis in einer innerHTML erhalten?

12

Ich habe dieses Layout, das dynamisch erstellt wurde:

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>

Und ich möchte, dass das Ereignis auf jedes klickt h4 class="name"und ein Protokoll mit der entsprechenden Nummer ianzeigt.

Allerdings console.logzeigt nur die letzten iverwandte ( i=9in diesem Fall), und funktioniert nicht mit den anderen iZahlen. Warum passiert das? Was muss ich tun?

Luiz Adorno
quelle
3
Vielleicht verwenden Sie Document.createElement (), anstatt HTML-String zu übergeben, und Sie werden es viel einfacher haben und ich denke, Sie werden am Ende besseren Code haben.
admcfajn
1
Sie müssen Ihre Knoten erneut durchlaufen und ein Ereignis anhängen, das Sie nicht an eine Zeichenfolge anhängen können
Eugen Sunic,
Schöne Frage, es ist sicher etwas schwierig. Ich denke, es ist möglich, ein Ereignis nach dem Rendern abzurufen. Zum Beispiel, nachdem Ihre forSchleife eine Methode aufgerufen hat , um Ereignisse auf diese Elemente anzuwenden, aber ich weiß es nicht. wird jsFiddle testen.
Pogrindis
Erstellt einen delegierten Ereignishandler für .card-bodyund überprüft, ob das Element ( target) ein Anker mit und eine ID ist, die mit beginnt title.
Hungerstar
1
Ah, ich bin gerade dabei, eine Antwort zu schreiben. Zur Wiedereröffnung gewählt. innerHTML += ...Schließungen sind nicht erforderlich, das Problem besteht darin , die zuvor festgelegten Ereignis-Listener zu töten, nicht die Schließung. Verwenden Sie document.createElementstattdessen. Siehe diesen Thread
ggorlen

Antworten:

8

innerHTMLZeichnet das vollständige HTML neu, wodurch alle zuvor angehängten Ereignisse verloren gehen. VerwendeninsertAdjacentHTML()

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
quelle
3
Wow .. Ich habe mit ähnlichen Dingen rumgespielt und bin nie auf etwas gestoßen insertAdjacentHTML- Everydays ein Schultag , wirklich nett.
Pogrindis
1
Sehr cool und +1, aber es scheint immer noch teuer und unnötig, es document.getElementByIdsofort nach dem Hinzufügen des HTML zu verwenden. Wenn Sie Hunderte von Elementen hinzufügen, ist dies eine Menge Durchquerungsarbeit.
Ggorlen
@ggorlen Ich stimme dem Perf-Bereich aus Interesse zu. nameWäre es effizienter , die Klasse zu verwenden und das Ereignis nur dieser Klasse zuzuordnen, während das Element durchlaufen wird (oder vielleicht nur aus dem Ereignis selbst zu lesen)?
Pogrindis
1
Nicht sicher. Hängt von der tatsächlichen Nutzung ab - mein Vorschlag könnte eine vorzeitige Optimierung sein. Sie müssten Profil / Benchmark und sehen. Aber wenn es document.createElementverwendet werden kann, denke ich, dass dies im Allgemeinen der beste Basisansatz ist. Eine weitere mögliche Verbesserung dieses Beitrags besteht darin, zwei Schleifen zu verwenden: eine zum Erstellen des gesamten HTML-Codes und eine weitere zum Abrufen aller Elemente mithilfe einer Klasse in einem document.querySelectorAllAufruf und Hinzufügen der Ereignis-Listener.
Ggorlen
1
Auf jeden Fall einen Benchmark wert, ich kann mir heute Abend nichts Besseres vorstellen! :)
Pogrindis
8

Die Verwendung von +=on innerHTMLzerstört Ereignis-Listener für die Elemente im HTML. Der richtige Weg, dies zu tun, ist zu verwenden document.createElement. Hier ist ein minimales, vollständiges Beispiel:

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

Dies ist hier natürlich document.getElementByIdnicht erforderlich, da wir das Objekt gerade im Schleifenblock erstellt haben. Dies spart potenziell teure Baumwanderungen.

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

Wenn Sie wie in Ihrem Beispiel über beliebige Abschnitte mit String-HTML verfügen, können Sie diese verwenden createElement("div"), innerHTMLeinmal auf den Block setzen und nach Bedarf Listener hinzufügen. Fügen Sie dann die divs als untergeordnete Elemente Ihres Containerelements hinzu.

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
quelle
1
Dies ist der Ansatz, über den ich nachgedacht habe, und ich würde dies zweifellos als Lösung bezeichnen, aber die andere Antwort ist auch großartig.
Pogrindis
Vielen Dank für die Antwort, aber ich habe versucht, das Layout mit createElement zu erstellen, und ich war erfolglos
Luiz Adorno
1
Kein Problem. Hoffentlich merkt man , wenn man einen großen Teil von HTML haben, können Sie ein Root - Element - Container wie ein erstellen kann <div>, dann div.innerHTML += yourHugeChunkOfHTML. Es gibt also keinen Grund, warum dies für keinen Anwendungsfall funktionieren sollte.
Ggorlen
1
Ich werde versuchen, dieses Layout mit Ihrer Empfehlung zu erstellen. Ich habe Angst, "document.getElementById" mehrmals zu verwenden und die Leistung zu verlangsamen
Luiz Adorno