動的に増える要素それぞれにイベントハンドラを設定したい
やりたいこと
-
querySelectorAll()
で取得できる要素が動的に増える場合でも、そのそれぞれにイベントハンドラを設定したい
どういうこと?
下記コードで最初に定義しているtaskCompletedButtons
は、別のイベントによって動的生成されるボタン要素たちです。それに加えて、新しく増えたボタンに対しても同じようにイベントハンドラを設定したいです。
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
とあるのが確認できます。
次に、「ボタン002」をクリックしました。target
欄をチェックすると、button.btn.btn-outline-secondary
となっており、クリックした要素を特定できていることを確認できます。
それでは、動的生成したボタンでも同じようにクリックした要素を特定できるか確認します。
ボタンを3つ生成したあと、「ボタン6」をクリックしました。target
の値だけでは判別できないので、もう少し詳しくみます。
innerText
の値をみます。「ボタン6」と表示されていることを確認できました。これで、動的生成した要素も取得できることがわかりました。
検証はこれで終わりです。
結果的に、ループを記述しなくて良くなりました。
書いてみれば、なんてことは無いように思えますが、検索しようと思うと急に難易度が上がるなあと思い記事として書きました。
おわり
Discussion