Rails Diary

プログラミングの学習記録です。

フォーム送信をAjax化する

Ajax実装する方法

pikawaka.com

↑こちらのサイトを参考に簡易的なメッセージアプリを作って、Ajaxがどういうものなのか体感してみた。具体的な実装はPikawakaさんの通りのため、記載していませんが個人的にメモしておきたっかたところをまとめています。

Ajaxを実装すると、画面遷移することなくデータを保存してページの一部だけを更新することができる。

今までは下記のようにlocal: trueを追加して同期通信をするように設定していた。

<%= form_with modle: @message, class: 'js-form' local: true do |f| %>
  <%= f.label :body %>
  <%= f.submit %>
<% end %>

form_withはデフォルトで非同期通信をするように設定されているため、非同期通信にしたいのであればloca: trueは記載しない。かつデフォルト設定のため、remote: trueを追加する必要もない。

<%= form_with modle: @message, class: 'js-form' do |f| %>
  <%= f.label :body %>
  <%= f.submit %>
<% end %>

Chromeの検証機能を用いて、HTMLにコンパイルされたコードを見てみると・・・

<form class="js-form" action="/messages" accept-charset="UTF-8" date-remote="true" method="post">
</form>

date-remote="true"が追加されている。
これによりフォームを送信する際にHTML形式ではなく、JS形式で送信することができる。

試しにコントローラのcreateアクションにbinding.pryを記述し、ブラウザでフォームからデータを送信してみる。ターミナルでrequest.formtを入力すると、リクエストのフォーマット情報が返される。

https://i.gyazo.com/6d9ff034d243ead16a964935864b7083.png

↑最後の行がapplication/javascriptになっている。フォームからのリクエストがJS形式で送信されている。

form_withにlocal: trueをつけてrequest.formatを調べてみるとapplication/xhtml+xmlになり、リクエストがHTML形式で送信されていることがわかる。

リクエストのフォーマットによって処理を分ける

リクエスト⇄レスポンスの流れは変わらず、リクエストに対応するコントローラ#アクションの処理が実行される。そしてアクション名を同名のビューファイルが呼び出される。

リクエストの形式がどうであれ、ビューファイルを返す前に実行されるアクションは同じ。リスエストの形式ごとに処理を分けるにはrespond_toメソッドを使う。

リクエストの形式がHTMLであれば{アクション名}.html.erbファイルが返され、JS形式であれば{アクション名}.js.erbファイルを呼び出す。

respond_toメソッドとは?

リクエストされるフォーマットによって処理を分けることができるメソッド。

respond_to do |format|
  format.形式 {処理}
  format.形式 {処理}
end

上記のように記載することで、リクエストの形式によって処理を変更してくれる。

https://i.gyazo.com/cf227fbee601a77de9d033bbd6ef8405.png

処理を書く欄にbinding.pryを記載し、localhost:3000/messages.jsonでリクエストを送ってみる(末尾に.jsonをつけるとjson形式でリクエストが送れる)。binding.pryを記載した箇所で処理が止まるので、request.formtでリクエストの形式を見てみる。

https://i.gyazo.com/e16f699924abab011679d191a3cd4a61.png

json形式だと分かる。

HTML形式も同様に確認してみる。 respond_toの分岐のformat.html {bindind.pry}を記載。localhost:3000/messageとリクエストを送ってみる。binding.pryの箇所で処理が止まるので同様にrequest.formatを確認するとリクエストの形式がHTMLだとわかる。

https://i.gyazo.com/28030af29780bea4c711858e1d89be72.png

このように、リクエストを送る形式によって処理を変えることができる。

respond_toを使ってフォームの入力内容を変更する

① createアクションにrespond_toの分岐を追加する

def create
    @message = Message.new(message_params)

    respond_to do |format|
      if @message.save
        format.html { redirect_to @message, notice: "メッセージ作成成功" }
          # html形式の場合、showアクションを通じて詳細画面にリダイレクトする
        format.js 
          # JS形式の場合、create.js.erbが呼び出される
      else
        format.html { render :new, status: :unprocessable_entity } 
          # new.html.erbを表示
        format.js { render :errors }
      end
    end
  end

② JS形式で分岐したとき用にcreate.js.erbファイルを作成する

touch app/views/messages/create.js.erb

ファイル内にconsole.log("文字列")を記載してみる

console.log("アイウエオ");

ブラウザの検証でconsoleを見てみる

https://i.gyazo.com/6eed19e14803a72390a9aaeaaac4db02.png

Consoleのところに「アイウエオ」と表示されている。

create.js.erbファイルでは、JavaScriptを使った処理の記述とERB(Rubyの埋め込み)、インスタンス変数ができる。

フォームの入力内容を表示する処理の記述

登録したメッセージを既存リストに追加する

$ ('.messages').append("<li class='message'><%= @message.body %><li>");

appendメソッドによって、引数に指定した要素をある親要素の末尾に追加してくれる。 liのclassにmessageを指定しているのは、messageクラスが付与された親要素に引数の子要素を追加するため。つまり<ul class="messages">の子要素として記述した要素が追加されることになる・・・・

https://i.gyazo.com/8dbb4fcf9857ce962a1822c684460572.png

エラーが出てしまった😞

調べてみると「$ is not defined」はjQueryが読み込まれていないときに出るらしい。Rails5系からrails-ujsみたいなgemがjQueryの代わりをしてくれると思っていたので、$で書く場合はjQueryが必要なのだろうか・・・

gem 'jquery-ujs'

インストール後、application.jsに上2行を追加

//= require jquery
//= require jquery-ujs
//= require rails-ujs
//= require activestorage
//= require turbolinks
//= require_tree .

Sprockets::FileNotFound in Messages#index
うーん🤔

jqueryをインストールしてみましたが、私の場合、application.jsに//= require jquery-ujsを追加してしまうと上記のエラーが出てしまいました。jquery-ujsを除いて、//= require jqueryのみの記載だとエラーがなくなりました。。Qiitaに言及がありますがどうなんだろう??

【Rails入門】5分でできるjQuery導入方法!初心者向けにまとめました | 侍エンジニアブログ

Rails 5.1.4におけるjQueryの導入について - Qiita

進みます。

pikawakaの通り、フォームに入力した内容が画面遷移せずに一覧に表示されるようになりました。ただ保存後もフォーム内に文字が残ってしまっているためこれをどうにかします。

フォームに残ってしまう文字列をどうにかする

$ ('.messages').append("<li class='message'><%= @message.body %><li>");
$('.js-form')[0].reset();

↑下の1行を追加する。これでフォームの文字が消えるようになる。

resetメソッド

resetメソッドはフォームの内容をリセットするときに使う。

$('セレクタ名')[0].reset();

フォーム内の値を空にするのではなく、初期値に戻している。

他にval("")メソッドというものがあるが、こちらは値を空にしている。一つ一つのフォームに定義する必要があるため、全てのフォームにおいて入力後リセットしたい場合はresetメソッドを使う。

部分テンプレートを使う

$ ('.messages').append("<li class='message'><%= @message.body %><li>");

↑に記述したappendメソッドの引数は、メッセージ一覧を作る際に作成したメッセージパーシャルと一緒。このため、引数にパーシャルを渡して更新することも出来る。

$ ('.messages').append("<%= escape_javascript(render partial: 'messages/message', locals: { message: @message }) %>");

改行やエスケープ処理をしてくれるescape_javascriptはjというエイリアスを持っているので、それで省略可

$ ('.messages').append("<%= j(render partial: 'messages/message', locals: { message: @message }) %>");

escape_javascriptメソッド

改行やダブルクォート、シングルクォートをエスケープ出来るそうな

Rails5 で escape_javascript を使う – ためすう

escape_javascript | Railsドキュメント

あと、partialとlocalsオプションも省略可だったスッキリす🙆‍♀️

$ ('.messages').append("<%= j(render 'messages/message', message: @message) %>");

エラー処理の実装

基本的にエラー処理はhtml形式をレンダリングすることが多いが、非同期通信のJS形式でエラー処理を実装する。

① バリデーションを追加する
フォームの空送信を防ぐためにvalidates :body, presence: true追加

pikawakaの補足説明で初めて知ったけど、presenceは内部でblank?メソッドを実行しているので、空白(空文字・空白文字・false・nil)でないことを確認しているとのこと。バリデーションの内部構造を考えたことなかった。。

② ビューにエラーメッセージ表示箇所を追加

<div class='contents'>
  <div class="js-message-errors"></div>
  • form_withの上に追加
  • class名のs-message-errorsはjavascriptを使って操作するための目印(後でjavascriptを記載する)

③ errors.js.erbファイルを作成する

touch app/views/messages/errors.js.erb

repond_to内のif文の分岐で保存できなかった場合のformat.jsの処理に{ render :errors }と記載していたが、これによりerrors.js.erbが呼び出される。

④errors.js.erbに更新処理を記述する

$('.js-message-errors').replaceWith("<%= j(render 'layouts/validation_errors', instance:@message) %>");

フォーム入力の処理と同様に、$('.セレクタ名')を記載。これは先ほどform_withの上部にjavascript処理の目印として記載したdivのクラス名。

replaceWithで引数で渡すパーシャルに置き換える処理をする。@messageをinstanceとして使えるように渡す。

⑤ エラーのパーシャル作成

touch app/views/layouts/_validation_errors.html.erb

パーシャル内に以下記載

<% if instance.errors.full_messages.present? %>
  <div class="validates js-message-errors">
    <ul>
      <% instance.errors.full_messages.each do |msg| %>
        <li style="color: #bb4a4a;"><%= msg %></li>
      <% end %>
    </ul>
  </div>
<% end %>

この記載により、空送信した際のバリデーションエラーメッセージが出る。

このままだとエラーメッセージが残ったままになってしまうので、create.js.erbに以下を追加

$('.messages').append("<%= escape_javascript(render partial: 'messages/message',
    locals: { message: @message })%>");
$('.js-form')[0].reset();
$('.js-message-errors').empty();

保存成功した際に呼び出されるcreate.js.erbに、セレクタで指定したjs-message-errorsを空にする記載をすることで、フォーム入力成功時はエラーメッセージが表示されないようになる。

参考にしたサイト

【Rails】 remote: trueでフォーム送信をAjax実装する方法とは? | Pikawaka

【Rails】 respond_toメソッドの使い方まとめ | Pikawaka

Rails学習者にrails-ujsの動作説明したら感動された話 - INODEVLOG

Rails 5.1.4におけるjQueryの導入について - Qiita

Railsの作成中に Uncaught ReferenceError: $ is not defined がでた - Qiita

【JavaScript】 resetメソッドを使ってフォームをリセットしよう | Pikawaka