JavaScriptでドロップダウンを実装する

## はじめに
Javascript研修を通して、ドロップダウンメニューを実装する。

## コーディングルール
- ES6 (EcmaScript2015)以降のモダンな記法を使う
- 関数をモジュール化して使用する
- data 属性を使用してDOM要素を取得する

## 目指す動作
![Something went wrong]()

## コード(pug, scss)
すでにpugファイルとscssファイルは用意されており、JSファイルを作成する。
この2つのファイルはすでに用意されているもののため割愛。

```_dropdown.pug
.dropdown
  .dropdown__inner
    nav
      ul.dropdown__menu
        li.dropdown__menuItem(data-selector="dropdown-menu-item") ABOUT
          ul.dropdown__subMenu(data-selector="dropdown-sub-menu")
            li.dropdown__subMenuItem about 1
        li.dropdown__menuItem(data-selector="dropdown-menu-item") SERVICE
          ul.dropdown__subMenu(data-selector="dropdown-sub-menu")
            li.dropdown__subMenuItem service 1
            li.dropdown__subMenuItem service 2
        li.dropdown__menuItem(data-selector="dropdown-menu-item") NEWS
          ul.dropdown__subMenu(data-selector="dropdown-sub-menu")
            li.dropdown__subMenuItem news 1
            li.dropdown__subMenuItem news 2
            li.dropdown__subMenuItem news 3
        li.dropdown__menuItem(data-selector="dropdown-menu-item") CONTACT
          ul.dropdown__subMenu(data-selector="dropdown-sub-menu")
            li.dropdown__subMenuItem news 1
            li.dropdown__subMenuItem news 2
            li.dropdown__subMenuItem news 3
            li.dropdown__subMenuItem news 4
```

```_dropdown.scss
@use 'modules/vars';

.dropdown {
  display: flex;
  justify-content: center;
  padding-bottom: 216px;
  margin-top: 128px;

  &__menu {
    display: flex;
    background-color: vars.$themeWhite;
  }

  &__menuItem {
    position: relative;
    width: 140px;
    padding: 16px 0;
    font-family: 'League Gothic', sans-serif;
    font-size: 24px;
    text-align: center;
    letter-spacing: 0.1em;

    &:hover {
      color: vars.$themeWhite;
      background-color: vars.$themeBlack;
    }
  }

  &__subMenu {
    position: absolute;
    display: none;
    width: 140px;
    margin-top: 16px;
    background-color: vars.$themeBlack;

    &.--active {
      display: block;
    }
  }

  &__subMenuItem {
    padding: 16px 0;
    font-size: 20px;
    letter-spacing: 0.1em;
    border: 1px solid vars.$themeBlack;

    &:hover {
      color: vars.$themeBlack;
      background-color: vars.$themeWhite;
    }
  }
}
```

## JSコード解説
全文は最後に記述しています。
まず1つずつ実際のコードの解説をしていきます。

### 変数の定義
まずは、ドロップダウンメニューの操作に必要な変数を定義しています。
```javascript
const dropdownMenuItem = '[data-selector="dropdown-menu-item"]';
const dropdownSubMenu = '[data-selector="dropdown-sub-menu"]';
const ACTIVE = '--active';
```
ここでは3つの変数を定義しています。

- `dropdownMenuItem`：ドロップダウンメニューの項目を特定するためのセレクタ。
- `dropdownSubMenu`：サブメニューを特定するためのセレクタ。
- `ACTIVE`：サブメニューが表示されている状態を示すCSSクラス名。

上書きされる可能性の低い'ACTIVE'のみ大文字で記述しております。
他の定数はキャメルケースで書いています。
このあたり正解がわからないので、もしアドバイス等あれば教えてください。

https://qiita.com/rana_kualu/items/e912d94cebd5c7ef73af

### イベントハンドラの定義

次に、メニュー項目にマウスが乗ったときや外れたときの動作を定義します。

```javascript
const showSubMenu = (event) => {
  const subMenu = event.target.querySelector(dropdownSubMenu);
  subMenu.classList.add(ACTIVE);
};

const hideSubMenu = (event) => {
  const subMenu = event.target.querySelector(dropdownSubMenu);
  subMenu.classList.remove(ACTIVE);
};
```

`showSubMenu`関数は、メニュー項目にマウスが乗ったときにサブメニューを表示するための関数です。

#### `event.target`
event オブジェクトには、イベントが発生した要素情報が含まれています。
event.target で、マウスが乗ったメニュー項目の要素を取得します。

#### `querySelector(dropdownSubMenu)`
`querySelector`メソッドを使って、マウスが乗ったメニュー項目内のサブメニュー要素を取得します。

#### `classList.add(ACTIVE)`
取得したサブメニュー要素に `--active`クラスを追加します。
このクラスが付与されることで、CSSで定義されたスタイルによりサブメニューが表示されるようになります。


対照的に、`hideSubMenu`関数はサブメニューを非表示にするための関数です。

#### `event.target` と `querySelector(dropdownSubMenu)`
`showSubMenu` と同様に、イベントが発生したメニュー項目要素と、その中のサブメニュー要素を取得します。

#### `classList.remove(ACTIVE)`
取得したサブメニュー要素から `--active` クラスを削除します。
このクラスが削除されることで、サブメニューが非表示になります。


### イベントリスナーの追加
最後に、上記で定義したイベントハンドラをドロップダウンメニューの項目に追加しています。

```javascript
menuItems.forEach((item) => {
  item.addEventListener('mouseenter', showSubMenu);
  item.addEventListener('mouseleave', hideSubMenu);
});
```
ここでは、全てのメニュー項目に対して、マウスが乗ったときは `showSubMenu` を実行し、マウスが外れたときは `hideSubMenu` を実行するようにしています。

#### `menuItems.forEach((item) => {...})`

`menuItems` は、ドロップダウンメニューの項目をすべて含むDOM要素のコレクションです。
`forEach` は、このNodeList内の各要素に対して、指定された関数を実行するメソッドであり、
すべてのメニュー項目に対して何らかの操作を行うためのものです。(以下動作)

#### `item.addEventListener('mouseenter', showSubMenu)`
メニュー項目にマウスが乗ったときに発火するイベントリスナーを追加しています。
`mouseenter` は、要素の上にマウスが移動したときに発火するイベントです。
このイベントが発生すると、`showSubMenu` 関数が実行され、サブメニューが表示されます。

#### `item.addEventListener('mouseleave', hideSubMenu)`
この行は、メニュー項目からマウスが離れたときに発火するイベントリスナーを追加しています。
`mouseleave` は、要素からマウスが離れたときに発火するイベントです。
このイベントが発生すると、`hideSubMenu` 関数が実行され、サブメニューが非表示になります。

### モジュール化処理を実行し、index.jsでimportする

最終行で下記コマンドを打つことで、モジュール化したJSを外部からimportできるようになり、
それをindex.js でimportしてあげます。

```javascript
export default dropdown;
```

```javascript
import dropdown from './modules/dropdown';

dropdown();
```



## 最終的なJSコード
```dropdown.js
/**
 * ドロップダウンメニューを操作する関数
 * @function dropdown
 */
const dropdown = () => {
  /**
   * 'dropdown-menu-item'のdata-selector属性を指定する文字列
   * @type {string}
   */
  const dropdownMenuItem = '[data-selector="dropdown-menu-item"]';

  /**
   * 'dropdown-sub-menu'のdata-selector属性を指定する文字列
   * @type {string}
   */
  const dropdownSubMenu = '[data-selector="dropdown-sub-menu"]';

  /**
   * サブメニューの表示状態を制御するCSSクラス名
   * @type {string}
   */
  const ACTIVE = '--active';

  /**
   * 'dropdown-menu-item'のdata-selector属性を持つすべてのDOM要素を取得
   * @type {NodeList}
   */
  const menuItems = document.querySelectorAll(dropdownMenuItem);

  /**
   * 指定したメニュー項目のサブメニューを表示
   * @param {Event} event - マウスを置いたときのイベント
   */
  const showSubMenu = (event) => {
    const subMenu = event.target.querySelector(dropdownSubMenu);
    subMenu.classList.add(ACTIVE);
  };

  /**
   * 指定したメニュー項目のサブメニューを非表示にする
   * @param {Event} event - マウスを外したときのイベント
   */
  const hideSubMenu = (event) => {
    const subMenu = event.target.querySelector(dropdownSubMenu);
    subMenu.classList.remove(ACTIVE);
  };

  menuItems.forEach((item) => {
    item.addEventListener('mouseenter', showSubMenu);
    item.addEventListener('mouseleave', hideSubMenu);
  });
};

export default dropdown;

```

```index.js
import dropdown from './modules/dropdown';

dropdown();
```

## まとめ
初めてJavaScriptの機能をしっかりと学んで実装したが、JSDocを書いてあげることで型を宣言することができたり、個人的に要素の意味を理解しやすかったりした。
定数に関して、`const`は変数と違い上書き不可だと思っていたが、実は絶対ではないとのこと。使い分けがまだ難しい。。慣れるしかない。