Jak mogę uzyskać zdarzenie kliknięcia wewnątrz innerHTML?

12

Mam ten układ, który został utworzony dynamicznie:

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>

I chcę, aby zdarzenie kliknęło na każde z nich h4 class="name"i pokazało dziennik z ipowiązanym numerem .

Jednak console.logpokazuje tylko ostatni ipokrewnych ( i=9w tym przypadku), i nie działa z innymi inumerami. Dlaczego to się dzieje? Co mam do zrobienia?

Luiz Adorno
źródło
3
Może użyj Document.createElement () zamiast przekazywać ciąg html i będziesz miał dużo łatwiejszy czas i myślę, że w końcu skończy się na lepszym kodzie.
admcfajn
1
będziesz musiał ponownie zapętlić węzły i dołączyć wydarzenie, którego nie możesz dołączyć do łańcucha
Eugen Sunic
Ładne pytanie, na pewno trochę trudne, myślę, że może być możliwe dołączenie renderowania zdarzenia po renderowaniu. Na przykład po forwywołaniu przez pętlę metody zastosowania zdarzeń do tych elementów, ale nie wiem. będzie jsFiddle do testowania.
Pogrindis
Utworzono moduł obsługi zdarzeń delegowanych .card-bodyi sprawdź, czy element ( target) jest zakotwiczeniem i identyfikatorem rozpoczynającym się od title.
hungerstar
1
Ach, właśnie zamierzam opublikować odpowiedź. Zagłosowano ponownie otworzyć. Zamknięcia nie są konieczne, problemem jest innerHTML += ...zabicie wcześniej ustawionych detektorów zdarzeń, a nie zamknięcie. Użyj document.createElementzamiast tego. Zobacz ten wątek
ggorlen

Odpowiedzi:

8

innerHTMLprzerysowuje pełny HTML, w wyniku czego wszystkie wcześniej dołączone zdarzenia zostaną utracone. Posługiwać sięinsertAdjacentHTML()

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
źródło
3
Wow ... Zająłem się podobnymi rzeczami i nigdy nie wpadłem na siebie insertAdjacentHTML- codziennie schoolday, naprawdę miło.
Pogrindis
1
Bardzo fajne i daje +1, ale nadal wydaje się drogie i niepotrzebne, aby użyć go document.getElementByIdnatychmiast po dodaniu HTML. Jeśli dodajesz setki elementów, jest to dużo pracy w ruchu.
ggorlen
@ggorlen Zgadzam się co do tego, że obszar zainteresowań nie byłby zainteresowany. Czy użycie klasy namei przypisanie zdarzenia do tej klasy podczas przechodzenia przez element (a może po prostu czytanie z samego wydarzenia) byłoby bardziej wydajne?
Pogrindis
1
Niepewny. Zależy od faktycznego użycia - moja sugestia może być przedwczesną optymalizacją. Musisz profilować / testować i zobaczyć. Ale jeśli document.createElementmożna go zastosować, myślę, że jest to ogólnie najlepsze podejście podstawowe. Innym możliwym ulepszeniem tego postu jest użycie dwóch pętli: jednej do zbudowania całego kodu HTML, a drugiej do pobrania wszystkich elementów za pomocą klasy w jednym document.querySelectorAllwywołaniu i dodania detektorów zdarzeń.
ggorlen
1
Zdecydowanie warte porównania, nie mogę wymyślić nic lepszego do zrobienia tej nocy! :)
Pogrindis
8

Użycie +=on innerHTMLniszczy detektory zdarzeń dotyczące elementów wewnątrz HTML. Prawidłowym sposobem na to jest użycie document.createElement. Oto minimalny, kompletny przykład:

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

Oczywiście document.getElementByIdnie jest to konieczne, ponieważ właśnie utworzyliśmy obiekt w bloku pętli. Oszczędza to potencjalnie drogie spacery po drzewach.

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

Jeśli masz dowolne fragmenty strunowego HTML, tak jak w twoim przykładzie, możesz go użyć createElement("div"), ustaw go innerHTMLraz na fragment i dodaj słuchacze w razie potrzeby. Następnie dołącz div jako dzieci elementu kontenera.

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
źródło
1
Takie podejście rozważałem i podałbym to bez problemu, ale druga odpowiedź też jest świetna.
Pogrindis
Dzięki za odpowiedź, ale próbowałem zbudować układ z createElement i nie udało mi się
Luiz Adorno
1
Nie ma problemu. Mam nadzieję, że zdajesz sobie sprawę, jeśli masz duży kawałek HTML można tworzyć pojemnik elementu głównego niczym <div>, potem div.innerHTML += yourHugeChunkOfHTML. Więc nie ma powodu, dla którego nie powinno to działać w żadnym przypadku użycia.
ggorlen
1
Spróbuję zbudować ten układ zgodnie z twoją rekomendacją, boję się kilka razy użyć „document.getElementById” i spowolnić działanie
Luiz Adorno