3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Ruby on Rails5】フラッシュメッセージをいい感じに表示させる。

Last updated at Posted at 2020-11-18

環境

Ruby 2.5.7
Rails 5.2.4

前提

形を問わずフラッシュメッセージの表示が確認できる状態。
turbolinksは切っています。オンの状態で動作確認はしていません。

gem

gem 'devise'

経緯

タイトルに「いい感じ」と表現しましたが、他に言葉が見つかりませんでした・・・笑
ユーザーが何かアクションを起こした時に、それをお知らせする方法としてページ内へのフラッシュメッセージの埋め込みがありますが、それをただ表示させるのは物足りないと感じて今回の形を実装してみました。(AbemaTVのアプリから発想を得ました笑)

目標

ログイン・ログアウトをメインに、アクションを起こすごとに下の方からニュッっとお知らせが一定時間出てくる。
ezgif.com-gif-maker (2).gif

手順

1.お知らせ窓layouts/_flash_window.html.erbを作成する
2.cssでお知らせ窓をいい感じにする
3.コントローラーでflash[:notice]を作成する
4.お知らせ窓専用のjsファイルassets/javascripts/flash_window.jsを作成する

1.お知らせ窓layouts/_flash_window.html.erbを作成する

お知らせ窓views/layouts/_flash_window.html.erbを部分テンプレートとして作成します。
今回は簡単なのでapplication.html.erbに直接書いてもいいのですが、窓の中身が今後複雑になったり、基本的に見えていない部分なので最初から分けておいてもいいかと思います。

layouts/_flash_window.html.erb

<div class="flash-window hidden">
  <p><%= notice %><span class="flash-window--delete">X</span></p>
</div>

CSSクラス名にhidddenを付けていますが、これがついているときは非表示、これがjs(toggle)で外れると表示される仕組みです。
2行目はnoticeメッセージ(フラッシュメッセージ)の後ろにX(バツボタン)を表示させることでお知らせ窓が自動で閉じる前に任意で閉じることができるようにしていきます。

ここで作成した部分テンプレートをapplication.html.erbに反映させていきます。

application.html.erb

...

<main>
  <div class="main">
    <%= yield %>
    <%= render partial: 'layouts/flash_window' %>
  </div>
</main>

...

<main>タグの下に<div>を挟んでいる理由としては、お知らせ窓の位置を<main>直下の<div>に基点にするためです。
<main>タグを起点にしても良かったのですが、後述するcssにmainを指定してしまうと思わぬところで依存関係ができてしまいそうでしたので、今回はお知らせ窓用に<div>を作った形です。
また、<main>タグの中に書くことで、どこのページでもお知らせ窓が表示されるようになります。

2.cssでお知らせ窓をいい感じにする

作成したお知らせ窓をcssでいい感じにデザインします。
これは一例なので、お好きにカスタマイズしてください。
(scssで記述していきます。)

assets/stylesheets/application.scss

main {
  .main {
    .flash-window  {
      // 窓の形、色、文字の形、色
      height: 50px;
      padding: 10px;
      color: white;
      background: black;
      border-radius: 10px;

      // 中のp要素(メッセージ)を上下左右中央揃え
      display: flex;
      justify-content: center;
      align-items: center;

      // 表示・非表示にかける時間
      transition: 0.5s 0s ease;

      // 窓の位置調整
      position: fixed;
      bottom: 40px;
      left: 0;
      right: 0;
      margin: auto;

      p {
        color: white;

        span {
          margin-left: 1vw;
          color: white;

          &:hover {
            cursor: pointer;
          }
        }
      }

      // 窓が非表示の時の状態
      &.hidden {
        opacity: 0;
        bottom: -50px;
      }
    }
  }
}

@media (min-width: PC, タブレット共通) {
  main {
    .main {
      .flash-window  {
        // 大画面の時は窓の横幅は固定
        width: 500px;
      }
    }
  }
}
@media (min-width: スマホ) {
  main {
    .main {
      .flash-window  {
        // 小画面の時は画面サイズの90%で可変
        width: 90%;
        p {
          font-size: 4vw;
        }
      }
    }
  }
}

CSSセレクタのhiddenが付くと"非表示"と言いましたが、実際はbottom: -50px;で要素が画面外に隠れているだけです。
さらにopacity: 0;も指定しているので、hiddenの時は完全に透明になっている状態です。
なので画面内に現れる時は透明の状態から具現化し、画面外にいく(非表示になる)時は段々と透明になっていきます。

3.コントローラーでflash[:notice]を作成する

今回はredirect後のフラッシュメッセージ(ログイン後TOPページにリダイレクトと同時にお知らせが出現など)を想定しています。
render後のフラッシュメッセージ(バリデーションエラーによるrender&エラーメッセージ)については最後に触れます。

users_controller.rb

...

def update
  if current_user.update(user_params)
    flash[:notice] = '会員情報を更新しました。'
    redirect_to user_path(current_user)
  else
    render :edit
  end
end

...

ここでは会員情報の更新時に会員詳細にリダイレクト&フラッシュメッセージの表示をする記述をしましたが、必要なところがあれば適宜行ってください。
基本的にはflash[:notice] = '内容'でnoticeキーにフラッシュメッセージを格納したあとに、redirect_toで任意のページに遷移することで、遷移先で先ほど格納したフラッシュメッセージが表示されるようになります。
ちなみに、deviseを使った新規登録・ログイン・ログアウトの処理はdevise内部で自動的にflash[:notice] = 'ログインしました。'等が格納されるので、自分で記述する必要はありません。

4.お知らせ窓専用のjsファイルassets/javascripts/flash_window.jsを作成する

最後に専用のjsファイルを作成していきます。(既存のapplication.jsから切り分ける理由は後述します。)

flash_window.js

addEventListener('load', ()=> {
  const flashWindow = document.getElementsByClassName("flash-window")[0];
  flashWindowToggle();
  setTimeout(flashWindowToggle, 3000)
  document.getElementsByClassName("flash-window--delete")[0].addEventListener('click', flashWindowToggle);
  function flashWindowToggle() {
    flashWindow.classList.toggle("hidden");
  }
});

addEventListenerのイベントハンドラにloadを指定しています。
これはページの読み込みが全て完了した時点でイベントを発生させないと、ページが読み込み中(白紙)の時に先にお知らせウィンドウだけが表示されてしまうからです。
toggleメソッドを使ったCSSクラスのhiddenの追加・削除は関数化しています。
この関数が呼び出されると、hiddenが削除されお知らせ窓が表示されます。
その後、この関数が3000ms(3秒)後に自動的に呼び出されるか、X(バツボタン)がクリックされると、hiddenが追加されます。

そして、このjsファイルはflash[:notice]に値が入っている時だけ発火させたいので、設定を少し変えていきます。

application.js

...

//= stub flash_window #ここを追加
//= require_tree .

...

application.jsの冒頭の読み込み設定で//= stub flash_windowを書くことで、flash_window.jsがプリコンパイルで結合されるjsファイルから除外されます。
しかし、この状態ではflash_window.js自体がプリコンパイルされないことになってしまうので、config/initializers/assets.rbに以下を追記します。

config/initializers/assets.rb

...

# 以下を追加
Rails.application.config.assets.precompile += %w( flash_window.js )

...

これにより、flash_window.jsのプリコンパイルを明示できます。

しかし、このままではプリコンパイルはされてもapplication.jsには結合されていないので読み込まれません。
そこでapplication.html.erb<head>タグ内にflash_window.jsの読み込みタグを直接追加する必要があります。

layouts/application.html.erb

<head>

...

<%= javascript_include_tag 'application', defer: true %>

# 以下を追加
<% if flash[:notice] %>
  <%= javascript_include_tag 'flash_window' %>
<% end %>

...

</head>

このようにapplication.jsからflash_window.jsを切り離すことでif文でflash[:notice]がある時だけflash_window.jsが読み込まれるという動作を作ることができました。
defer: trueオプションを付けていない理由は、今回はflash_window.js内のイベントがページ読み込み完了後であることをaddEventListener('load', ()=>{...});という形で直接書いているためです。
このファイルに他のイベントを書く場合はdefer: trueが必要になるかも知れませんので、そこは適宜お願いします。

以上で、目的の動作が達成されます。

ここからは番外編です!

番外編

番外編その1.不要なフラッシュメッセージが表示される場合

deviseのcreate, update, destroyアクションにはほとんどの場合フラッシュメッセージflash[:notice]が格納されているため、不要な場面でも窓が出てきてしまうことがあります。
その場合の対処法を書いていきます。

application_controller.rb

...

protected

...

# deviseで作成されたflashのリセット
def delete_devise_flash_messages
  flash[:notice] = nil
end

application_controller.rbでこのメソッドを定義し、不要なフラッシュメッセージが表示されるコントローラー内でbefore_action :delete_devise_flash_messagesまたはafter_action :delete_devise_flash_messagesを書くとフラッシュメッセージflash[:notice]が表示されなくなります。(:notice以外のキーは残ります。)

私の場合は新規登録の場面で利用しています。

入力内容の確認画面から認証メールの送信アクション、そして"メールを確認してください。"というページに遷移するのですが、そのページ内でメールについて詳しい説明を書いているのに、改めてフラッシュメッセージで"メールを確認してください"は重複するので不要です。
そこで下記のような記述をしています。

registrations_controller.rb

class Users::RegistrationsController < Devise::RegistrationsController

  before_action :delete_devise_flash_messages, only: %i[email_notice]

  # 入力された内容を確認するページ
  def confirm
  end

  # 認証メールが送信されたことをお知らせするページ
  def email_notice
  end

  # 登録が完了したことをお知らせするページ
  def complete
  end

end

これで認証メールの送信をお知らせするページemail_noticeアクションのみflash[:notice]のリセットが働いてフラッシュメッセージが表示されなくなります。

番外編その2.エラーのフラッシュメッセージに対応する方法

ここまではflash[:notice]に格納されたメッセージのみを取り扱ってきましたが、バリデーションで発生したエラーなども表示したい場合はflash[:alert]を追加していきます。
さらにフラッシュメッセージごとに挙動を変更したい場合はflash[:error]flash[:success]なんかも自作されてみるといいでしょう。
変更箇所は主に2カ所です。

layouts/application.html.erb

...

<%= javascript_include_tag 'application', defer: true %>

<%# if文に || flash[:alert]を追加 %>
<% if flash[:notice] || flash[:alert] %>
  <%= javascript_include_tag 'flash_window' %>
<% end %>

...

if文に|| flash[:alert]キーを追加します。
これにより:noticeキーもしくは:alertキーに値があるとflash_window.jsが発火するようになります。

次に部分テンプレート内にもalertを追加をしていきます。

layouts/_flash_window.html.erb

<div class="flash-window hidden">
  <% if flash[:notice] %>
    <p><%= notice %><span class="flash-window--delete">X</span></p>
  <% end %>
  <% if flash[:alert] %>
    <p><%= alert %><span class="flash-window--delete">X</span></p>
  <% end %>
</div>

部分テンプレートにもif文を書くことで表示や動作などの細かい部分を分けることができるようになります。

まとめ

今回ご紹介した物はベースとなると考えていますので、動作などはお好きなようにカスタムしていただければと思います^^
みなさまの参考になれば幸いです。

また、質問や解釈の違い、記述方法に違和感ありましたら、コメント等でご指摘いただけると幸いです。

私のTwitterでも毎日このようなテクニックや感想・考察を発信していますので、もしご興味があれば一度覗いてみてください(´ー`)
Twitter - @Masao_Sasaki_ae

最後まで読んでいただきありがとうございました!

参考サイト

より実用的な使い方については、私のGitHubに実際に使っているファイルを公開しているのでこちらも参考にしていただければと思います!
GitHub - MasaoSasaki/matchi

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?