【Rails、JavaScript】モーダルウィンドウに写真詳細画面を表示させる

# 概要
一覧の写真をクリックすると、モーダルウィンドウ内に写真詳細画面が表示される機能を実装します。

# 参照
### モーダルウィンドウ

基本的なモーダルウィンドウの作成方法とアニメーションの付け方は、これらの記事を参考にしました。

- JavaScriptでモーダルウィンドウを作ろう（ドットインストール）
https://dotinstall.com/lessons/modal_js_v3

- モーダルウィンドウ・ダイアログウィンドウの作り方１（HTML、CSS、JavaScriptで作る）
https://www.youtube.com/watch?v=BZX6RxQzgrQ
- モーダルウィンドウ・ダイアログウィンドウの作り方２（HTML、CSS、JavaScriptで作る）
https://www.youtube.com/watch?v=AXVaTCCs7Ew&t=250s

### Ajax
remote: trueによるAjaxの実装方法は、この記事がわかりやすいです。

- 【Rails】remote: trueでフォーム送信をAjax実装する方法とは？  
https://pikawaka.com/rails/remote-true

# 完成イメージ
indexビューの写真をクリックするとshowビューの写真詳細画面がモーダル内に表示される。

[![Image from Gyazo](https://i.gyazo.com/c38bd4d1acf7335b5f801ef93f9260b0.gif)](https://gyazo.com/c38bd4d1acf7335b5f801ef93f9260b0)

# 開発環境
macOS Big Sur バージョン 11.2
ruby 2.6.5
Rails 6.0.3.4

# 実装の流れ
1. 仮のモーダルウィンドウを作成する。
1. 写真クリックでshowアクションが実行され、js.erbファイルが呼び出されるようにする。
1. JavaScriptにより、モーダルウィンドウ内にshowビューが格納され表示されるようにする。 

# 実装
## 1. 仮のモーダルウィンドウを作成する。
indexとshowアクションはphotosコントローラーで定義されています。

```photos_controller.rb
class PhotosController < ApplicationController
  省略
  def index
    省略
  end

  def show
    省略
  end
  省略
end
```

index.html.erbの適当な位置に仮のモーダルウィンドウを設置します。
hiddenクラスで最初は表示されないようにします。

```erb:index.html.erb
# モーダルの周囲を薄暗くするマスク
  <div id="mask" class="hidden"></div>
# モーダル
# あとからJavaScriptで<div>内のコードをshowビューのコードに置き換えます。
  <div id="modal" class="hidden">
    <p>モーダルウィンドウ<p>
  </div>
```

cssはこのようになります。詳細は参照動画を御覧ください。

```index.css
/* hiddenクラスがないとマスクとモーダルが表示される（JavaScriptでhiddenクラスを消したとき）*/
#mask {
  background-color: rgba(0, 0, 0, 0.5);
  position: fixed;
  top: 0;
  bottom: 0;
  right: 0;
  left: 0;
  z-index: 1;
}

#modal {
  position: fixed;   /* スクロールで移動しないようmodalの位置を固定します */
  top: 10vh;
  /* 以下3行：モーダルを左右中央揃えにする方法 */
  left: 0;
  right: 0;
  margin: 0 auto;
  transform: translate(0, 0) scale(1);
  transition: 0.2s;   /* transitionでアニメーションを作成 */
  z-index: 2;   /* マスクより上に重なるようにする */
  width: 935px;   /* showビューの幅に合わせて設定 */
}

/* hiddenクラスで最初は表示されないようにする */
#mask.hidden {
  opacity: 0;
  visibility: hidden;
}

#modal.hidden {
  opacity: 0;
  visibility: hidden;
  transform: translate(0, 0) scale(0.8);   /* 大きさ0.8倍から等倍にモーダルが拡大される */
}
```

## 2. 写真クリックでshowアクションが実行され、js.erbファイルが呼び出されるようにする。

一覧の写真は部分テンプレートにしてあります。
`link_to`メソッドでshowアクションが実行され、`remote: true`を設定することにより、リクエストをjs形式にします。

```erb:index.html.erb
# 部分テンプレートを呼び出す
<%= render partial:"shared/photos", locals: {photos: @photos} %>
```

```erb:_photos.html.erb
<% photos.each do |photo| %>
# 写真をクリックしたときの実行アクション（showなのでphoto_path(photo.id)）とリクエスト形式（jsなのでremote: true） を指定
  <%= link_to photo_path(photo.id), remote: true do %>
    <%= image_tag photo.image.variant(省略), class: "photo-img", id: "photo-img" %>
　<% end %>
<% end %>
```

```photos_controller.rb
class PhotosController < ApplicationController
  省略

  def show
    省略
    respond_to do |format|
      format.html
      # link_toメソッドをremote: trueに設定したのでリクエストはjs形式で行われる（詳しくは参照記事をご覧ください）
      format.js
    end
  end
  省略
end
```

## 3. JavaScriptにより、モーダルウィンドウ内にshowビューが格納され表示されるようにする。 
showビューを取得するために、show.html.erbを部分テンプレート化します

```erb:_show.html.erb
（省略）（中身はshow.html.erbと同じ）
```

上記showアクションが実行されると、photosフォルダ内のshow.js.erbファイルが呼び出されます。ない場合は作成します。

```erb:show.js.erb
# モーダルを取得
var modal = document.getElementById('modal');
# マスクを取得
var mask = document.getElementById('mask');

# モーダルの中身をshowビューに置き換えます。
# 部分テンプレートのパス、localsの値はアプリにより異なります。
modal.innerHTML = '<%= escape_javascript(render partial: 'shared/photo_show', locals: { photo: @photo, comments: @comments, user: @user, comment: @comment }) %>';

# hiddenクラスを消してモーダルとマスクを表示させます。
modal.classList.remove('hidden');
mask.classList.remove('hidden');

# モーダルの外側（マスク）をクリックするとhiddenクラスが書き込まれ、モーダルとマスクが再び非表示となります。
mask.addEventListener('click', () => {
  modal.classList.add('hidden');
  mask.classList.add('hidden');
  });
```

# おわりに

注意点として、モーダル内で"いいね"や"コメント投稿"などを実行する場合は、いずれもAjax化しておく必要があります。Ajax化されていない場合、それらを実行すると他のページにリダイレクトされたりエラーとなったりします。

↓Ajax化されていると、このようにモーダル内で"いいね"や"コメント投稿"ができます。

[![Image from Gyazo](https://i.gyazo.com/739a3c0e44498ec00f0ccedfd943fc60.gif)](https://gyazo.com/739a3c0e44498ec00f0ccedfd943fc60)

以上、参考になりましたら幸いです。
間違いなどありましたら、ご指摘いただきたいです。