【Rails/JavaScript】非同期のフラッシュメッセージ表示

## はじめに
非同期処理に合わせて、Javascriptでフラッシュメッセージも非同期的に表示する実装についてまとめました。

## 実装内容
### フラッシュメッセージを表示する関数
jsの各メソッドについては、後述の[解説](#解説)にまとめています。
```js
  function displayFlashMessage(data) {
    const div = document.createElement('div');
    div.className = 'flash-message';
    div.textContent = data.message;

    const flashContainer = document.querySelector('.flash-message-container');
    flashContainer.appendChild(div);
    div.classList.add(data.message_type);

    //js側でstyleを設定（画面の中央に固定で配置する）
    flashContainer.style.position = 'fixed';
    flashContainer.style.top = '50%';
    flashContainer.style.left = '50%';
    flashContainer.style.opacity = 0.9;
    flashContainer.style.zIndex = 99;
    // jqueryのfadeOutメソッドで5秒後に非表示
    $(div).fadeOut(5000);
  };

```

### フラッシュメッセージの実装
パーシャル（共通のファイル）として、他のビューファイルからも呼び出して使えるようにしています。
```app/views/shared/_flash_message.html.erb
<div>
  <% flash.each do |message_type, message| %>
    <div class="flash-message <%= message_type %>"><%= message %></div>
  <% end %>
</div>
```

### 他のビューファイルで呼び出す
```app/views/events/index.html.erb
<div class="flash-message-container">
  <%= render 'shared/flash_message' %>
</div>
```

### サーバー側
```app/controllers/events_controller.rb
  def destroy
    @event.discard
    if @event.discarded?
      render json: { status: 'success', message_type: 'notice', message: "イベント削除しました", event_data: @event }, status: 200
    else
      render json: { status: 'error', message_type: 'alert', message: "イベント削除に失敗しました" }, status: 400
    end
  end
```
- 処理が成功（もしくは失敗）した場合のフラッシュメッセージのタイプとメッセージ内容をJSONで送ります。
- message_typeとmessageは、[前述のフラッシュメッセージ](#フラッシュメッセージの実装)と同じキー名を設定しています。

### フロント側
前提として、今回は[fetch](https://developer.mozilla.org/ja/docs/Web/API/fetch)によってDELETEリクエストを送信しています。
```flash.js
    try {
        const url = `http://localhost:3000/events/1`;

        const response = await fetch(url, {
            method: 'DELETE',
            headers: {
                'Content-Type': 'application/json',
                'Accept': 'application/json'
            },
        });

        if(response.ok) {
            // JSONを受け取り、JavaScriptオブジェクトにparseします（awaitでparseが完了するまで待つ）
            const data = await response.json();
            // フラッシュメッセージの関数を呼び出す
            displayFlashMessage(data);
        } else {
            const data = await response.json();
            displayFlashMessage(data);
        }
    } catch {
      console.error( e.name, e.message );
    }
```

## 解説
前述の[フラッシュメッセージ関数内の処理](#フラッシュメッセージを表示する関数)をメインに解説しています。
### [Document.createElement()](https://developer.mozilla.org/ja/docs/Web/API/Document/createElement)
引数に指定されたHTML要素を生成します（ここでは```<div>```を生成しています）
生成した```div```はローカル変数(定数)へ格納しています。
```js
const div = document.createElement('div');
```

### [className](https://developer.mozilla.org/ja/docs/Web/API/Element/className)
class属性を取得または設定します。
```js
div.className = 'flash-message';
```

### [textContent](https://developer.mozilla.org/ja/docs/Web/API/Node/textContent)
ノード内のテキストを取得または設定します。
[ノードとは、「HTML（DOMツリー）の一つ一つの要素」](https://zenn.dev/sqer/articles/2d4def0f07bf04c5cc47#element)のことです。
ここでは、先ほどclass属性```flash-message```を設定したdivタグに、[サーバーから返却されたレスポンス内のmessageの値](#サーバー側)を設定しています。
```js
div.textContent = data.message;
```
（補足）
開発者ツールの[要素タブでノードを指定>プロパティ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/677000/562b1826-aae6-cced-2934-56df976500ca.png)を選択するとtextContentに値が入っていることが確認できました。


### [querySelector()](https://developer.mozilla.org/ja/docs/Web/API/Document/querySelector)
指定された [CSS セレクタ](https://saruwakakun.com/html-css/reference/selector#section1)の最初の要素を返します。
ここでは[フラッシュメッセージを呼び出すファイル](#他のビューファイルで呼び出す)のdivに指定されたclass属性を取得しています。
```js
const flashContainer = document.querySelector('.flash-message-container');
```

### [appendChild()](https://developer.mozilla.org/ja/docs/Web/API/Node/appendChild)
特定の親ノードの子ノードリストの末尾にノードを追加します。
先ほどのflashContainer変数に```div```を追加します。
class属性```flash-message```とtextContentの中身が入った```div```タグを指しています。
```js
flashContainer.appendChild(div);
```

### [classList](https://developer.mozilla.org/ja/docs/Web/API/Element/classList)
特定の要素のクラス名を追加、削除、参照などすることができるプロパティです。
今回は```classList.add```で先ほどの```div```に対して、[サーバーからのmessage_type](#サーバー側)をclass名として追加しています。
```js
div.classList.add(data.message_type);
```

(補足)
追加されたclass属性は[前述のフラッシュメッセージ](#フラッシュメッセージの実装)の```flash-message```に付与されます
```js
<div class="flash-message <%= message_type %>">
```


## 動作確認結果
イベントを削除した後のフラッシュメッセージ表示/非表示の例です。
![asyncflash.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/677000/4cadf2ba-68a9-801f-09b0-54e8fc8b5303.gif)


## まとめ
実装を通じてflashの仕組みやJavaScriptのいろんなプロパティやメソッドへの理解も深まりました。
JavaScriptの基本を理解したい方のお役に立てれば幸いです。

## 参考記事
- [【Rails】フラッシュメッセージ実装](https://zenn.dev/h_hana/articles/8d1062943ad6af)
- [【JS】async/await構文で書いたFetch APIからJSONデータを読み込む](https://www.i-ryo.com/entry/2020/06/05/192657#asyncawait%E6%A7%8B%E6%96%87)
- [CSSのセレクタとは？覚えておきたい25種類と書き方](https://saruwakakun.com/html-css/reference/selector#section1)
- [DOM, Node, Elementの違い(Zenn)](https://zenn.dev/sqer/articles/2d4def0f07bf04c5cc47#element)
- [DOM、Node、Elementを理解する(Qiita)](https://qiita.com/andota05/items/a2292d2b7780ed5faa31)