【JavaScript】イベントキャプチャリング、イベントバブリングについて図解で理解する ２　 〜 イベント移譲 〜


前回の記事でイベントバブリングによって、親要素にイベントを伝播させることを説明しました。

[【JavaScript】イベントキャプチャリング、イベントバブリングについて図解で理解する。](https://qiita.com/sho_U/items/b11916cece5658ddf2e9)

これを利用して各要素にイベントを登録するのではなく、親要素にイベントを一括で登録しておくことができます。
これを**イベント移譲**といいます。

今、下記の様な実装を行ってみます。

![cc3e5d57697de820477b21913a8ad5dd (6).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547556/96fbb23e-646a-071a-eab5-d30654fa5a0b.gif)


ボタンをクリックすることにより、アラートされます。サーチを押すとインプットフォームが出現します。

以下のように各ボタンにイベントを登録して実装しました。

```html
<!DOCTYPE HTML>
<html>
<body>

  <div id="menu">
    <button id="save">セーブ</button>
    <button id="load">ロード</button>
    <button id="search">サーチ</button>
    <input id="searchForm" hidden />
  </div>

  <script>

    function alertSave() {
      alert("セーブしました。");
    }
    function alertLoad() {
      alert("ロードしました。");
    }
    function displayForm() {
      searchForm.hidden = !searchForm.hidden
    }

    save.addEventListener("click", () => { alertSave() });
    load.addEventListener("click", () => { alertLoad() });
    search.addEventListener("click", () => { displayForm() });
  </script>
</body>

</html>

<style>
  body {
    background-color: gray;
  }

  #menu {
    display: flex;
    width: 400px;
    justify-content: space-between;
  }
</style>
```

これをイベントバブリングを利用して親要素(menu)で一括で登録してみます。

```html
<body>

  <div id="menu">
    <button id="save">セーブ</button>
    <button id="load">ロード</button>
    <button id="search">サーチ</button>
    <input id="searchForm" hidden />
  </div>

  <script>

    function buttonAction(ev) {
      switch (ev.target.id) {
        case "save":
          alert("セーブしました。");
          break;
        case "load":
          alert("ロードしました。");
          break;
        case "search":
          searchForm.hidden = !searchForm.hidden
          break;
        default:
          alert("どのボタンでもありません");
          break;
      }
    }

    menu.addEventListener("click", (ev) => { buttonAction(ev) });
  </script>
</body>
```
子要素で発生したクリックは、親に伝播して伝わり(イベントバブリング)、親要素でクリックイベントを登録しているため実行されます。
この様にイベントを親に移譲することにより、ボタンの数を増やしたりした場合でもわざわざ各要素にイベントを登録必要がなくなります。

仮にmenuではなく、documentにイベントを登録しておけば、どこにでも要素を配置することができます。

```html
<body>

  <div id="menu">
    <button id="save">セーブ</button>
    <button id="load">ロード</button>
    <button id="search">サーチ</button>
    <input id="searchForm" hidden />
  </div>

  <!--  登録ボタンを配置 -->
  <button id="register">登録</button>

  <script>

    function buttonAction(ev) {
      switch (ev.target.id) {
        case "save":
          alert("セーブしました。");
          break;
        case "load":
          alert("ロードしました。");
          break;
        case "search":
          searchForm.hidden = !searchForm.hidden
          break;
        case "register":
          alert("登録しました")
          break;
        default:
          alert("どのボタンでもありません");
          break;
      }
    }
    //  documentにイベントを登録したので、どこにボタンを配置しても発火 
    document.addEventListener("click", (ev) => { buttonAction(ev) });
  </script>
</body>
```

ただし、現状問題点があります。
例えば、ボタンの文字内にさらに子要素が存在し、それをクリックした場合は意図した動作しません。

```html
 <button id="save">セーブ<span>!!</span></button>

ビックリマークボタンがクリックされた場合は、親要素でクリックイベントを検知し、buttonActionは発火するが、ev.targetは、あくまでspan要素となるのでidが"save"ではない。
```
以下の様にケアします。

```js
    function buttonAction(ev) {
      const btn = ev.target.closest('button');   
    // クリック発生源の直近のbutton要素を取得してidを取得する。

      switch (btn.id) {
        case "save":
          alert("セーブしました。");
          break;
        case "load":
          alert("ロードしました。");
          break;
        case "search":
          searchForm.hidden = !searchForm.hidden
          break;
        case "register":
          alert("登録しました");
          break;
        default:
          alert("どのボタンでもありません");
          break;
      }
    }
```