動的に増える要素それぞれにイベントハンドラを設定したい

10787 ワード

やりたいこと

  • querySelectorAll()で取得できる要素が動的に増える場合でも、そのそれぞれにイベントハンドラを設定したい

どういうこと?

下記コードで最初に定義しているtaskCompletedButtonsは、別のイベントによって動的生成されるボタン要素たちです。それに加えて、新しく増えたボタンに対しても同じようにイベントハンドラを設定したいです。

改善前.js
const taskCompletedButtons = document.querySelectorAll('.btn-completed');
taskCompletedButtons.forEach((e) => {
    e.addEventListener('click', (j) => {
        if(j.target.className === 'Hello') {
            // なにか処理がはいります
        }
    });
});

解決策

イベント移譲(Event Delegation)を使用することで対応可能です。

これは、イベントハンドラを、設定したい要素そのものに適用せず、要素のコンテナーに適用することを意味します。
先程載せたコードでは、動的に数が増えるボタンそのものにハンドラを設定していました。そのままでは、追加されたボタンがループの対象に入らないために、イベントハンドラの設定も行われません。

そこで、ボタンを包括する要素(コンテナー)に対してイベントハンドラを設定します。
HTMLはこんな具合です。li要素を含む、ボタン要素が動的に増える対象です。

<ul id="container" class="list-group list-group-flush">
  <li class="list-group-item"><button class="btn btn-outline-primary">ボタン001</button></li>
  <li class="list-group-item"><button class="btn btn-outline-secondary">ボタン002</button></li>
  <li class="list-group-item"><button class="btn btn-outline-success">ボタン003</button></li>
</ul>

最初に載せたコードを書き直して、動的に生成されたボタンもイベント対象にします。
※ボタンの動的生成部分は割愛します。

const listRootElm = document.getElementById('container');
listRootElm.addEventListener('click', (e) => {
  console.log(e);
});

書き直したコードにconsole.log()を仕込んでみました。動的生成した要素をクリックしたときに、ちゃんと反応してくれるかを確認します。その前に、最初から存在する要素でテストしてみます。

まずは、ul要素の直下に位置するli要素をクリックしました。target欄にli.list-group-itemとあるのが確認できます。
開発者ツールとウェブページを同時に開いた状態のスクリーンショット。画面左側にウェブページ。細い線のボタンが縦に3つ並んでいる。その一番下はボタンを含めて、横長い範囲に青い背景を伴っている。ボタンの下にはツールチップが表示され、青い背景になっている箇所を指し示して、ボタンに関する情報を表示している。「li.list-group-item 320×54」画面右側に開発者ツール。下部に緑枠線。「target: li.list-group-item」を囲っている。

次に、「ボタン002」をクリックしました。target欄をチェックすると、button.btn.btn-outline-secondaryとなっており、クリックした要素を特定できていることを確認できます。
開発者ツールとウェブページを同時に開いた状態のスクリーンショット。画面左側にウェブページ。細い線のボタンが縦に2つ並んでいる。その一番下は青い背景を伴っている。ボタンの下にはツールチップが表示され、青い背景になっている箇所を指し示して、ボタンに関する情報を表示している。「button.btn.btn-outline-secondary 89.23×38」画面右側に開発者ツール。下部に緑枠線。「target: button.btn.btn-outline-secondary」を囲っている。

それでは、動的生成したボタンでも同じようにクリックした要素を特定できるか確認します。

ボタンを3つ生成したあと、「ボタン6」をクリックしました。targetの値だけでは判別できないので、もう少し詳しくみます。
開発者ツールとウェブページを同時に開いた状態のスクリーンショット。画面左側にウェブページ。最上部に黒枠「ボタンを追加する」。その左下に細い線のボタンが配置されている。ボタンの下にはツールチップ、その下には青い背景を伴ったボタンが表示されている。ツールチップは、青い背景になっている箇所を指し示して、ボタンに関する情報を表示している。「button.btn.btn-dark 71.98×38」画面右側に開発者ツール。下部に緑枠線。「target: button.btn.btn-dark」を囲っている。

innerTextの値をみます。「ボタン6」と表示されていることを確認できました。これで、動的生成した要素も取得できることがわかりました。
開発者ツールとウェブページを同時に開いた状態のスクリーンショット。画面左側にウェブページ。最上部に黒枠「ボタンを追加する」。その左下に細い線のボタンが配置されている。ボタンの下にはツールチップ、その下には青い背景を伴ったボタンが表示されている。ツールチップは、青い背景になっている箇所を指し示して、ボタンに関する情報を表示している。「button.btn.btn-dark 71.98×38」画面右側に開発者ツール。下部に緑枠線。「innerText: "ボタン6"」を囲っている。

検証はこれで終わりです。

結果的に、ループを記述しなくて良くなりました。
書いてみれば、なんてことは無いように思えますが、検索しようと思うと急に難易度が上がるなあと思い記事として書きました。

おわり

実際に書いたコード

検証に使用したコードをCodepenにアップしています