Как я могу получить событие click внутри innerHTML?

12

У меня есть этот макет, который был создан динамически:

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>

И я хочу, чтобы на каждом событии щелкнуло мышью h4 class="name"и показать журнал с соответствующим номером i.

Тем не менее, console.logпоказывает только последний iсвязанный ( i=9в этом случае), и не работает с другими iчислами. Почему это происходит? Что мне нужно сделать?

Луис Адорно
источник
3
Возможно, используйте Document.createElement () вместо передачи html-строки, и вам будет намного легче, и я думаю, что в итоге вы получите лучший код.
admcfajn
1
вам придется снова пройтись по узлам и прикрепить событие, которое вы не можете прикрепить к строке
Eugen Sunic
Хороший вопрос, это довольно сложно, я думаю, что возможно было бы возможно связать рендеринг события после события. Например, после того, как ваш forцикл вызвал метод для применения событий к этим элементам, но я не знаю. будет jsFiddle для тестирования.
Погриндис
Создал делегированный обработчик события .card-bodyи проверил, является ли element ( target) привязкой с идентификатором, который начинается с title.
голодовка
1
Ах, вот-вот отправлю ответ. Проголосовал за открытие. Замыкания не нужны, проблема в том, чтобы innerHTML += ...убить ранее установленные прослушиватели событий, а не замыкание. Используйте document.createElementвместо этого. Посмотри эту тему
ggorlen

Ответы:

8

innerHTMLперерисовывает полный html, в результате все ранее прикрепленные события теряются. использование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>

Мамун
источник
3
Ух ты ... Я возился с подобными вещами и никогда не сталкивался с этим insertAdjacentHTML- Каждый день в школе, очень мило.
Погриндис
1
Очень круто и +1, но все равно кажется дорогим и ненужным использовать document.getElementByIdсразу после добавления HTML. Если вы добавляете сотни элементов, это очень трудоемкая работа.
Ггорлен
@ggorlen Я вроде бы согласен с тем, что область перфектов из интереса будет nameболее эффективной, если использование класса и привязка события только к этому классу при прохождении через элемент (или, может быть, просто чтение из самого события)?
Погриндис
1
Точно сказать не могу. Зависит от фактического использования - моё предложение может быть преждевременной оптимизацией. Вы должны были бы профиль / тест и посмотреть. Но если document.createElementможно использовать, я думаю, что в целом это лучший базовый подход. Еще одно возможное улучшение этого поста - использовать два цикла: один для построения всего HTML-кода, а другой - для захвата всех элементов с помощью класса за один document.querySelectorAllвызов и добавления прослушивателей событий.
Ггорлен
1
Определенно стоящий эталон, я не могу придумать ничего лучшего, чем заняться сегодня вечером! :)
Погриндис
8

Использование +=on innerHTMLуничтожает слушатели событий на элементах внутри HTML. Правильный способ сделать это - использовать document.createElement. Вот минимальный, полный пример:

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

Конечно, в document.getElementByIdэтом нет необходимости, поскольку мы только что создали объект в блоке цикла. Это спасает потенциально дорогие прогулки по деревьям.

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

Если у вас есть произвольные куски строкового HTML, как в вашем примере, вы можете использовать его createElement("div"), установите его innerHTMLодин раз на кусочек и добавьте слушателей по мере необходимости. Затем добавьте div как дочерний элемент вашего контейнера.

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
источник
1
Это подход, который я рассматривал, и я бы дал это решение, но другой ответ тоже великолепен.
Погриндис
Спасибо за ответ, но я попытался создать макет с помощью createElement, и мне это не удалось
Луис Адорно
1
Нет проблем. Надеюсь, вы понимаете, что если у вас большой кусок HTML, вы можете создать контейнер корневого элемента, например <div>, тогда div.innerHTML += yourHugeChunkOfHTML. Так что нет никаких причин, по которым это не должно работать ни для одного варианта использования.
Ггорлен
1
Я постараюсь построить этот макет с вашей рекомендацией, я боюсь использовать «document.getElementById» несколько раз и замедлить производительность
Луиз Адорно