Rails 5.2 + Bulmaでajaxを使ってモーダルウィンドウUX

**[Rails 5.2 で簡単なWikiサイトを作る](https://qiita.com/fukmaru/items/625845471df2110cc718) - Part4**

[Rails 5.2 で簡単なWikiサイトを作る - リストViewを作る](https://qiita.com/fukmaru/items/318bc4ef1fa5b80e4c9a)の続きです。

# 環境

- `Rails 5.2.2`
- `ruby 2.5.1p57`
- `psql (PostgreSQL) 10.4`

# 前提条件

本記事では、Rails、ruby、PGのインストールの内容を含みません。

- ruby のインストールは、[こちら](https://qiita.com/Alex_mht_code/items/d2db2eba17830e36a5f1)の記事がわかりやすかったです。
- Ruby on Rails のインストールは、[こちら](https://qiita.com/yoshiokatsuneo@github/items/d75d4fef9e75afb65126#rails%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB)の記事がわかりやすかったです。

## CSSフレームワークは、bulmaを利用してます。
bulmaを導入する方法は[こちら](https://qiita.com/fukmaru/items/cd9c1c9c8658ac4a0a89#_reference-cab064a3295de148dcde)!

# はじめに

[git commit log](https://github.com/fukmaru/wiki/commit/b3ff8476227292cce958e0b7f47008a7f2a2d24f)

## 始める前に 

CSS をネストで書きたいので `postcss-nested` を導入します。

```bash
yarn add postcss-nested
```

# ルーティングを作ります

```rb:config/routes.rb
Rails.application.routes.draw do
-   get 'contents/index'
+   resources :contents
  root to: "pages#home"
  # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
end

```

**現在の全てのパスはこんな感じ**

```bash
$ rails routes
                   Prefix Verb   URI Pattern                                                                              Controller#Action
                 contents GET    /contents(.:format)                                                                      contents#index
                          POST   /contents(.:format)                                                                      contents#create
              new_content GET    /contents/new(.:format)                                                                  contents#new
             edit_content GET    /contents/:id/edit(.:format)                                                             contents#edit
                  content GET    /contents/:id(.:format)                                                                  contents#show
                          PATCH  /contents/:id(.:format)                                                                  contents#update
                          PUT    /contents/:id(.:format)                                                                  contents#update
                          DELETE /contents/:id(.:format)                                                                  contents#destroy
                     root GET    /                                                                                        pages#home
       rails_service_blob GET    /rails/active_storage/blobs/:signed_id/*filename(.:format)                               active_storage/blobs#show
rails_blob_representation GET    /rails/active_storage/representations/:signed_blob_id/:variation_key/*filename(.:format) active_storage/representations#show
       rails_disk_service GET    /rails/active_storage/disk/:encoded_key/*filename(.:format)                              active_storage/disk#show
update_rails_disk_service PUT    /rails/active_storage/disk/:encoded_token(.:format)                                      active_storage/disk#update
     rails_direct_uploads POST   /rails/active_storage/direct_uploads(.:format)                                           active_storage/direct_uploads#create
```

`data-remote="true"` でajaxを使えるようにするために、 `rails-ujs` を使います。

```bash
yarn add rails-ujs
```

```js:./frontend/packs/application.js
import "init";
import "components/page/page"
import "components/contents/contents"

+ import Rails from 'rails-ujs';
+ Rails.start();
```

# 新規作成ボタンを作ります

```erb:./frontend/components/contents/_contents.html.erb
<div class="contents js-contents">
  ...
  <div class="contents--create-button js-contents--create-button">
    <%= link_to "+", new_content_path, remote: true, class: "contents--create-button--circle js-contents--create-button--circle button is-primary" %>
  </div>
</div>
```

```css:./frontend/components/contents/contents.css
.contents { /* ボタンがテーブルにかからないように */
    padding-bottom: 60px;

    &--create-button { /* ボタンを右下に固定 */
         position: fixed;
         bottom: 5px;
         right: 5px;
         padding: 6px 10px;

        &--circle { /* ボタンを丸にして、中の「+」テキストをちょっと大きく */
             height: 60px;
             width: 60px;
             border-radius: 50%;
             font-size: 1.5em;
        }
    }
}
```

**確認してみます**

<http://localhost:5000/contents/index>

こんな感じになります

<img width="914" alt="スクリーンショット 2019-04-29 19.10.59.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263973/dabf4aa9-9161-2c78-81d8-18cb23e645e3.png">



# モーダルで表示する作成ウィンドウを作ります

まずは、コンポーネントを作ります。

```bash
$ mkdir -p frontend/components/content-form
$ touch frontend/components/content-form/{content-form.css,content-form.js,_content-form.html.erb}
```

```js:./frontend/components/contents/contents.js
import "./contents.css"
import "components/content/content"
+ import "components/content-form/content-form"
```

```js:./frontend/components/content-form/content-form.js
import "./content-form.css"
```

モーダルViewを作ります。

```js:./frontend/components/contents/_contents.html.erb
<div class="contents js-contents">
  ...
  <div class="contents-content-modal js-contents-content-modal modal">
    <div class="modal-background"></div>
    <div class="modal-card">
      <header class="modal-card-head">
        <p class="modal-card-title">コンテンツ</p>
        <button class="delete js-content-form--close-button"></button>
      </header>
      <div class="contents--content-form js-contents--content-form"></div>
    </div>
  </div>
</div>
```

```erb:./frontend/components/content-form/_content-form.html.erb
<%= form_with model: content do |form| %>
  <section class="modal-card-body">
    <div class="field">
      <label class="label">タイトル</label>
      <div class="control">
        <%= form.text_field :title, class: "input", placeholder: "Poii.ioはどんなサービスですか？" %>
      </div>
      <p class="help">タイトルを設定します。</p>
    </div>

    <div class="field">
      <label class="label">本文</label>
      <div class="control">
        <%= form.text_area :body, class: "textarea", rows: "3", placeholder: "Poii.ioとは、Wikiサービスです。様々なチャネルから検索することができます。" %>
      </div>
      <p class="help">本文を設定します。</p>
    </div>

    <div class="field">
      <div class="control">
        <label class="checkbox">
          <%= form.check_box :is_published %>
          公開しますか？
        </label>
      </div>
    </div>
  </section>
  <footer class="modal-card-foot">
    <%= form.submit content.id.present? ? "編集" : "作成", class: "button is-success" %>
  </footer>
<% end %>
```

:coffee:
`form_for`, `form_tag` は、Rails6で廃止されるようです。`form_with`を初めて使いました:scream_cat:

新規作成ボタンをクリックした時の処理を書きます

```rb:./app/controllers/contents_controller.rb
class ContentsController < ApplicationController
  ...
  def new
    @content = Content.new
  end
end
```

```js:./app/views/contents/new.js.erb
document.querySelector(".js-contents--content-form").innerHTML = "<%= escape_javascript(c("content-form", content: @content)) %>";
document.querySelector(".modal").classList.add("is-active");
```

## Controllerに作成時の処理を書く

```text
Parameters: {"utf8"=>"✓", "authenticity_token"=>"G8KTmI4hUI5NT6BNR9mVwdP+gk/mgfGWd71IJJ9J7xo1C2G15r9jtSB3d29rhdokevYy5Ybb5dFeIwa+4LIP5Q==", "content"=>{"title"=>"テストタイトル", "body"=>"テスト本文テスト本文テスト本文テスト本文テスト本文テスト本文テスト本文テスト本文テスト本文v"}, "commit"=>"作成"}
```

上の `params` に合わせて、データを取得し、保存します。

```rb:./app/controllers/contents_controller.rb
class ContentsController < ApplicationController
  ...

  def create
    @content = Content.new(content_params)
    @content.save
  end

  private
    def content_params
      params.require(:content).permit(:title, :body, :is_published)
    end
end
```

```js:./app/views/contents/create.js.erb
document.querySelector(".js-contents--table--content-row").insertAdjacentHTML('beforeend', "<%= j(c("content", content: @content)) %>");
document.querySelector(".modal").classList.remove("is-active");
document.querySelector(".js-contents--content-form").innerHTML = "";
```


## 編集時にも同じウィンドウを表示します

リストにて、編集リンクを作ります。

```erb:./frontend/components/contents/_contents.html.erb
<div class="contents js-contents">
  <table class="contents--table js-contents--table table is-fullwidth">
    <thead>
    <tr>
      <th><abbr title="Id">#</abbr></th>
      <th><addr title="Title">Title</addr></th>
      <th><addr title="Body">Body</addr></th>
      <th><addr title="IsPublished">Published?</addr></th>
+       <th></th>
    </tr>
    </thead>
...
```

```erb:./frontend/components/content/_content.html.erb
<tr class="content content--row">
  <th><%= content.id %></th>
  <td><%= content.title %></td>
  <td><%= content.body %></td>
  <td><%= content.is_published %></td>
+   <td><%= link_to "編集", edit_content_path(content), remote: true, class: "button is-success is-small" %></td>
</tr>
```

こんな画面になります。

<img width="914" alt="スクリーンショット 2019-04-29 20.36.58.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263973/c1c5f7e8-9872-d005-0a63-6c5ad51ef2b4.png">

## ajaxで、編集用のモーダルを修正します

### Controllerの編集

```rb:./app/controllers/contents_controller.rb
class ContentsController < ApplicationController
+     before_action :set_content, only: [:edit, :update]

  ...
+   def edit
+   end

+   def update
+     @content.update(content_params)
+   end

  private
    ...
+     def set_content
+       @content = Content.find(params[:id])
+     end
end
```

### ajaxの処理

```bash
$ touch app/views/contents/edit.js.erb
```

```js:./app/views/contents/edit.js.erb
document.querySelector(".js-contents--content-form").innerHTML = "<%= escape_javascript(c("content-form", content: @content)) %>";
document.querySelector(".modal").classList.add("is-active");
```

```js:./app/views/contents/update.js.erb
document.querySelector(".js-content-row--<%= @content.id %>").innerHTML = "<%= j(c("content", content: @content)) %>";
document.querySelector(".modal").classList.remove("is-active");
document.querySelector(".js-contents--content-form").innerHTML = "";
```

# 削除機能をつくりましょう

## Controllerの編集

```rb:./app/controllers/contents_controller.rb
class ContentsController < ApplicationController
-   before_action :set_content, only: [:edit, :update]
+   before_action :set_content, only: [:edit, :update, :destroy]
  ...
+   def destroy
+     @content.delete
+     redirect_to contents_path
+   end
end
```

## Viewの修正

```erb:
<div class="contents js-contents">
  <table class="contents--table js-contents--table table is-fullwidth">
    <thead>
    <tr>
      <th><abbr title="Id">#</abbr></th>
      <th><addr title="Title">タイトル</addr></th>
      <th><addr title="Body">本文</addr></th>
      <th><addr title="IsPublished">公開</addr></th>
      <th></th>
+       <th></th>
    </tr>
```

```erb:
<tr class="content content--row js-content-row js-content-row--<%= content.id %>">
  <th><%= content.id %></th>
  <td><%= content.title %></td>
  <td><%= content.body %></td>
  <td><%= content.is_published %></td>
  <td><%= link_to "編集", edit_content_path(content), remote: true, class: "button is-success is-small" %></td>
+   <td><%= link_to "削除", content_path(content), method: :delete, class: "button is-danger is-small" %></td>
</tr>
```

こんな感じの画面になります。

<img width="921" alt="スクリーンショット 2019-05-03 03.10.36.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263973/18d89155-9cb9-bbf1-6ff3-439d83150277.png">



# 最後に

[git commit log](https://github.com/fukmaru/wiki/commit/b3ff8476227292cce958e0b7f47008a7f2a2d24f)

# Wikiサービス作りました

(小さく告知させてください:ear:)
簡単なWiki検索を、社内コミュニケーションチャネルから検索できるツールで、「[Poii.io](https://www.poii.io)」と言います。

# [NEXT!](https://qiita.com/fukmaru/items/c22ba2700e9ad368770c)

検索ページを実装していこうと思います。