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
たぶん、html-stringを渡す代わりにDocument.createElement()を使用してください。そうすれば、時間がはるかに簡単になり、最終的にはより良いコードが得られると思います。
admcfajn

1
ノードをもう一度ループして、文字列にアタッチできないイベントをアタッチする必要があります
Eugen Sunic

いい質問ですが、確かに少しトリッキーです。イベントポストレンダリングをアタッチできる可能性があると思います。たとえば、forループを呼び出してメソッドを呼び出して、これらの要素にイベントを適用することはできますが、わかりません。jsFiddleをテストします。
ポグリンディス

の委任イベントハンドラーを作成し.card-body、要素(target)がで始まり、IDがで始まるアンカーであるかどうかを確認しtitleます。
hungerstar

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

@ggorlen興味のないパフォーマンス領域について、クラスnameを使用して、要素を通過するときに(またはおそらくイベント自体から読み取るだけで)イベントをそのクラスだけにアタッチする方が効率的であることに同意します。
ポグリンディス

1
わからない。実際の使用法に依存します-私の提案は時期尚早の最適化かもしれません。あなたはプロファイリング/ベンチマークをして、見る必要があります。しかし、もしそれdocument.createElementが使用できれば、それが一般的に最良のベースラインアプローチだと思います。この投稿に対するもう1つの可能な改善点は、2つのループを使用することです。1つはすべてのHTMLを構築し、もう1つは1回のdocument.querySelectorAll呼び出しでクラスを使用してすべての要素を取得し、イベントリスナーを追加します。
ggorlen

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


1
これは私が検討していたアプローチであり、私はこれを解決策として提供しますが、他の答えも素晴らしいです。
ポグリンディス

de answerに感謝しますが、createElementでレイアウトを作成しようとしましたが、失敗しました
Luiz Adorno

1
問題ない。HTMLの大きなチャンクがある場合は、ルート要素コンテナを<div>、次にのように作成できますdiv.innerHTML += yourHugeChunkOfHTML。したがって、これがどのユースケースでも機能しない理由はありません。
ggorlen

1
私はあなたの推薦でこのレイアウトを構築しようとします、 "document.getElementById"を数回使用してパフォーマンスを遅くすることを恐れています
Luiz Adorno
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.