【Rails】Bootstrapを使用したモーダルのフォームの作成と、バリデーションエラー表示方法

Ruby on Railsアイキャッチ画像

やりたいこと

この新規登録機能を、ページ遷移無し、モーダル表示に変更する。

目標物

はじめに、作成しているアプリが、home画面にproductとcustomerの2つの情報を表示していることから、controllerとviewの構成が少し複雑になりますことご了承願います。

Bootstrapのこちらを基に実装していく

Modal
Use Bootstrap’s JavaScript modal plugin to add dialogs to your site for lightboxes, user notifications, or completely custom content.

ちなみに、Bootstrapをまんまコピーするとこちら

<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#exampleModal" data-whatever="@mdo">Open modal for @mdo</button>
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#exampleModal" data-whatever="@fat">Open modal for @fat</button>
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#exampleModal" data-whatever="@getbootstrap">Open modal for @getbootstrap</button>

<div class="modal fade" id="exampleModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
  <div class="modal-dialog">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title" id="exampleModalLabel">New message</h5>
        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
          <span aria-hidden="true">×</span>
        </button>
      </div>
      <div class="modal-body">
        <form>
          <div class="form-group">
            <label for="recipient-name" class="col-form-label">Recipient:</label>
            <input type="text" class="form-control" id="recipient-name">
          </div>
          <div class="form-group">
            <label for="message-text" class="col-form-label">Message:</label>
            <textarea class="form-control" id="message-text"></textarea>
          </div>
        </form>
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
        <button type="button" class="btn btn-primary">Send message</button>
      </div>
    </div>
  </div>
</div>

この中のよく分からないやつについて詳しく見てみる

data-toggle

カスタムデータ属性。何をするかを指定している。

この場合、”modal”

data-target

カスタムデータ属性。どの要素を開くかを指定している。

この場合、id=”exampleModal”

ボタンクリックで、id=”exampleModal”を持つモーダルが表示される。

data-whatever

カスタムデータ属性。

名前からすると、”何でも良い”

view: ボタンを変更

変更前

<div class="button_new">
  <%= link_to new_customer_path do %>
    <button type="button" class="btn btn-warning"><%= t('views.button.new_customer') %></button>
  <% end %>
</div>

変更後

<div class="button_new">
  <button type="button" class="btn btn-warning" data-toggle="modal" data-target="#customerModal" data-whatever="@customer"><%= t('views.button.new_customer') %></button>
</div>

view: モーダル表示部

変更前 

ページ遷移表示時のnewのview

<div class="container">
  <div class="customer_page">
    <div class="set_center_outer">
      <h2 class="page_title_en">New Customer</h2>
      <h5 class="page_title_ja"><%= t("views.title.new_customer") %></h5>
    </div>

    <%= render "form", customer: @customer %>

    <div class="set_center_outer">
      <p><%= link_to t("views.button.back_home"), homes_path, class: "hover_background_none" %></p>
    </div>
  </div>
</div>

editと共通部をパーシャル化していたので、その部分も記載すると、、

<%= form_with(model: customer, local: true) do |form| %>

  <div class="set_center_outer">
    <div class="set_center_inner">
      <% if customer.errors.any? %>
        <div id="error_explanation">
          <h2><%= customer.errors.count %> 件のエラーが発生しました</h2>
          <ul>
          <% customer.errors.full_messages.each do |message| %>
            <li><%= message %></li>
          <% end %>
          </ul>
        </div>
      <% end %>
    </div>
  </div>

  <div class="set_center_outer">
    <div class="set_center_inner">
      <div class="field">
        <%= form.label t('views.title.customer_number') %>
        <%= form.text_field :number, placeholder: '123-456' %>
      </div>

      <div class="field">
        <%= form.label t('views.title.customer_name') %>
        <%= form.text_field :name, placeholder: 'お客様名' %>
      </div>
    </div>

    <div class="actions">
      <%= form.submit %>
    </div>
  </div>

<% end %>

変更後

customerのnewビューでなく、homesのindexにモーダルコードを記述する。

<div class="modal fade" id="customerModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
  <div class="modal-dialog">
	  <div class="modal-content">
	  <div class="modal-header">
		  <button type="button" class="close" data-dismiss="modal" aria-label="Close">
		  <span aria-hidden="true">×</span>
		  </button>
	  </div>
		<div class="set_center_outer">
		 <h2 class="page_title_en">New Customer</h2>
		 <h5 class="page_title_ja"><%= t("views.title.new_customer") %></h5>
		</div>
		<%= render "modal_customer_form", customer: @customer %>
 	  </div>
  </div>
</div>

こちらもまたform部をパーシャル化しているので、そちらも記述します。

<%= form_with(model: customer, remote: true) do |form| %>

  <div id="modal_customer_messages-error">
    <%= render 'layouts/error_messages', model: form.object %>
  </div>

  <div class="set_center_outer">
    <div class="set_center_inner">
      <div class="field">
        <%= form.label t('views.title.customer_number') %>
        <%= form.text_field :number, placeholder: '123-456' %>
      </div>

      <div class="field">
        <%= form.label t('views.title.customer_name') %>
        <%= form.text_field :name, placeholder: 'お客様名' %>
      </div>
    </div>

    <div class="modal_submit">
      <%= form.submit %>
    </div>
  </div>

<% end %>

form_withの”local: true”を”remote:true”にしているのもポイント!

controller

homesのindexのview内でモーダル表示をすることにより、newアクションで@customerが生成されるため、homes_controllerのindexに@customerを追加する。

class HomesController < ApplicationController
  def index
    @product = Product.new
    @customer = Customer.new
    @products = Product.all
    @products = Product.search_product(params[:search]) if params[:search].present?
    @q = Customer.ransack(params[:q])
    @customers = @q.result(distinct: true).order(number: :asc)
  end
end

customers_controllerのcreateアクションのsaveできなかったときのバリデーションエラーメッセージを非同期で表示するためにcreate部のcontrollerを変更する。

変更前

def create
  @customer = current_user.customers.build(customer_params)

  respond_to do |format|
	  if @customer.save
	    format.html { redirect_to @customer, notice: t('views.messages.create_customer') }
	  else
	    format.html { render :new, status: :unprocessable_entity }
	  end
  end
end

変更後

def create
  @customer = current_user.customers.build(customer_params)

  respond_to do |format|
	  if @customer.save
	    format.html { redirect_to @customer, notice: t('views.messages.create_customer') }
	  else
	    format.js { render :error }
	  end
  end
end

js

エラーメッセージをjsにレンダリングしているためerrorのjsファイルを作成

$("#modal_customer_messages-error").html("<%= j(render 'layouts/error_messages', model: @customer) %>");

CSS

最後に、CSSを少し整える


.modal-header {
  border-bottom: none;
}

.modal-dialog {
  max-width: 400px;
}

.modal_submit {
  margin-bottom: 40px;
}

コメント