LoginSignup
2
4

More than 3 years have passed since last update.

コメント機能の非同期通信

Posted at

Gemfileの記述

Gemfileにjquery-railsが記載されているか確認し、記載されていない場合は最下部に記載してください。

〜省略〜
# Use jquery as the JavaScript library
gem 'jquery-rails'
〜省略〜

記述したときはターミナルで

bundle install

application.jsの記述を確認

application.jsに
//= require jquery
//= require rails-ujs
の記載がされているか確認します。なければ以下のように記述してください。

application.js
省略
//= require jquery
//= require rails-ujs 
//= require_tree .

必要なクラス名とID

ajaxの非同期通信を実装するために、事前にコメントのフォームにクラス名を与えておきます。
仮に
new_commentというid
textboxというclass
form__submitというclassをつけるとします。
【例】モデル名をTweetとします。

app/views/tweets/show.html.erb
<%= form_with(model: [@tweet, @comment], local: true, id: "new_comment") do |form| %> 
  <%= form.text_area :text, placeholder: "コメントする" , rows: "2", class: "textbox" %>
  <%= form.submit "送信", class: "form__submit" %>
<% end %>

コメント機能を非同期通信化

1.jQueryを記述するためのファイルを作成する
2.フォームが送信されたら、イベントが発火するようにする
3.非同期通信でコメントが保存されるようにする
4.respond_toを使用してHTMLとJSONの場合で処理を分ける
5.jbuilderを使用して、作成したメッセージをJSON形式で返す
6.返ってきたJSONをdoneメソッドで受取り、HTMLを作成する
7.エラー時の処理を行う
を作業していきます。

jQueryを記述するためのファイルを作成

JavaScriptファイルをassets/javascripts以下に作成します。今回はコメント機能の作成なのでcomment.jsのファイルを作ります。

フォームが送信されたら、イベントが発火

submitボタンを押した時に、イベントが発火するようにします。
comment.jsに、コメントフォームが送信された時フォームに入力された値を受け取れるようにします。
【例】

comment.js
$(function(){
  $('#new_comment').on('submit', function(e){
    e.preventDefault();
    var formData = new FormData(this);
  })
})

フォームが送信された時、というイベントを設定したい場合は、form要素を取得してonメソッドを使います。
今回のアプリの画面ではform要素のid属性が#new_commentにしています。フォームの送信についてonメソッドでイベントをセッティングする際は、form要素自体に設定するようにします。
最初から設定されているフォームの動作はキャンセルします。
フォームが送信される時、何も設定していない状態(デフォルトの状態)だとフォームを送信するための通信が行われるため、preventDefault()を使用してデフォルトのイベントを止めます。

FormData

フォームのデータの送信に使用することができます。その他にも、キーのついたデータを伝送するためにフォームとは独立して使用することもできます。今回はコメントフォームがあるので、そのフォームの情報を取得するのに使います。
new FormData(フォーム要素)とすることでFormDataを作成できます。
今回FormDataオブジェクトの引数はthisとなっていますが、イベントで設定したfunction内でthisを利用した場合は、イベントが発生したノード要素を指します。今回の場合は、new_commentというIDがついたフォームの情報を取得しています。

フォームの値を確認

comment.js
$(function(){
  $('#new_comment').on('submit', function(e){
    e.preventDefault();
    console.log(this)   #追加部分
    var formData = new FormData(this);
  })
})

検証の画面でコンソールを開き、thisの中身がフォームに入力した値が出力されていればOKです。
JavaScriptではconsole.logを用いてデバッグを行います。

非同期通信でコメントを保存

フォームに入力されたデータを取得したら、必要なAjax関数のオプションを揃えて非同期通信を行います。
1.フォームの送信が行われた時に、Ajaxによる非同期通信でcreateアクションを動かす
2.createアクション内でコメントを保存し、respond_toを使用してHTMLとJSONの場合で処理を分ける
console.log(this)は削除します。

comment.js
$(function(){
  $('#new_comment').on('submit', function(e){
    e.preventDefault();
    var formData = new FormData(this);   #追加
    var url = $(this).attr('action')
    $.ajax({
      url: url,
      type: "POST",
      data: formData,
      dataType: 'json',
      processData: false,
      contentType: false   #〜追加
    })
  })
})

フォームの送信先のurlを定義しています。$(this)は、thisで取得できる要素をjQueryオブジェクト化しています。

attrメソッド

要素が持つ指定属性の値を返します。
要素が指定属性を持っていない場合、関数はundefinedを返します。
今回はイベントが発生した要素のaction属性の値を取得しており、今回のaction属性にはフォームの送信先のurlの値が入っています。
これでリクエストを送信する先のURLを定義することができました。

processDataオプション

デフォルトではtrueになっており、dataに指定したオブジェクトをクエリ文字列(例: msg.txt?b1=%E3%81%8B&b2=%E3%81%8D )に変換する役割があります。
クエリ文字列とは、WebブラウザなどがWebサーバに送信するデータをURLの末尾に特定の形式で表記したものです。

contentTypeオプション

サーバにデータのファイル形式を伝えるヘッダです。こちらはデフォルトでは「text/xml」でコンテンツタイプをXMLとして返してきます。
ajaxのリクエストがFormDataのときはどちらの値も適切な状態で送ることが可能なため、falseにすることで設定が上書きされることを防ぎます。FormDataをつかってフォームの情報を取得した時には必ずfalseにするという認識です。
他にもAjaxリクエストを送信するオプションの参考です。
(http://js.studio-kingdom.com/jquery/ajax/ajax)

コメントを保存し、respond_toを使用してHTMLとJSONの場合で処理を分ける

非同期通信によって、comment#createを動かすことに成功し、正しくAjaxからdataを送れていれば、そのまま保存することが可能です。
保存ができていることを確認したら、respond_toを使用してHTMLとJSONの場合で処理を書いていきます。
【例】上のcomments_controller.rbが同期通信です。それを非同期通信の記述に編集します。

comments_controller.rb
class CommentsController < ApplicationController
def create
  Comment.create(comment_params)
end

private
def comment_params
  params.require(:comment).permit(:text).merge(user_id: current_user.id, tweet_id: params[:tweet_id])
  end
end
comments_controller.rb
def create
    @comment = Comment.create(comment_params)   #編集〜
    respond_to do |format|
      format.html { redirect_to tweet_path(params[:tweet_id])  }
      format.json
    end   #〜編集
  end

  private
 def comment_params
    params.require(:comment).permit(:text).merge(user_id: current_user.id, tweet_id: params[:tweet_id])
 end

ローカル変数commentは、スコープの関係でこの後のjbuilder側で使用できないので、インスタンス変数@commentに編集します。
respond_to doを使用し、リクエストされたformatによって処理を分けます。フォーマットがjsonの時は、format.jsonに引数としてインスタンスを渡します。今回はjbuilderを使ってJavaScriptに返すデータを作成します。

jbuilder

rails newコマンドでアプリケーションを作成した際にgemfileにデフォルトで記述されているgemで、入力データをJSON形式で出力するテンプレートエンジンです。
Jbuilderを利用するときは、コントローラ内でrender json: ○○を行いません。

jbuilderを使用して、作成したメッセージをJSON形式で返す

respond_toで処理を分けたら、jbuilderを使用してJavaScriptファイルに返すデータを作成します。
jbuilderは、viewと同じように該当するアクションと同じ名前にします。
commentのcreateアクションに対応するjbuilderのファイルになるので、作成するのはviews/comments/create.json.jbuilderになります。
【例】今回はコメント欄にtextとuser.idとuser.nicknameがあるとします。

views/comments/create.json.jbuilder
json.text  @comment.text
json.user_id  @comment.user.id
json.user_name  @comment.user.nickname

jbuilderファイルでは基本的にjson.KEY VALUEという形で書くことができます。
こうすることによってJavaScriptファイルに返ってきたデータをjbuilderで定義したキーとバリューの形で呼び出して使うことができます。

返ってきたJSONをdoneメソッドで受取り、HTMLを作成する

非同期通信の結果として返ってくるデータは、done(function(data) { 処理 })の関数の引数で受け取ります。

comment.js
$(function(){
  function buildHTML(comment){   #追加
    var html = `<p>
                  <strong>
                    <a href=/users/${comment.user_id}>${comment.user_name}</a>
                    :
                  </strong>
                  ${comment.text}
                </p>`
    return html;
  }   #〜追加
  $('#new_comment').on('submit', function(e){
    e.preventDefault();
    var formData = new FormData(this);
    var url = $(this).attr('action');
    $.ajax({
      url: url,
      type: "POST",
      data: formData,
      dataType: 'json',
      processData: false,
      contentType: false
    })
    .done(function(data){   #追加
      var html = buildHTML(data);
      $('.comments').append(html);
      $('.textbox').val('');
      $('.form__submit').prop('disabled', false);
    })   #〜追加
  })
});

.done〜は、非同期通信に成功した時の記述です。function(data)となっていますが、非同期通信に成功した時の即時関数の第一引数には、サーバから返されたデータが入っています。この場合のサーバから返ってくるデータというのは、jbuilderで作成したcreate.json.jbuilderのデータです。
$('.form__submit').prop('disabled', false);は、 htmlの仕様でsubmitボタンを一度押したらdisabled属性というボタンが押せなく属性が追加されます。
そのため、disabled属性をfalseにする記述を追加しています。
function buildHTML(comment){〜はHTMLを追加しています。

テンプレートリテラル記法

ダブルクオートやシングルクオートの代わりにバックティック文字(`)で囲むことで、複数行文字列や文字列内挿入機能を使用できます。
buildHTMLの引数として渡されたcommentはサーバから返されたデータであるjbuilderのデータであるため、ファイル内で定義したキーとバリューの形式で使用できます。

エラー時の処理を行う

comment.js
省略
.done(function(data){
      var html = buildHTML(data);
      $('.comments').append(html);
      $('.textbox').val('');
      $('.form__submit').prop('disabled', false);
    })
    .fail(function(){   #追加
      alert('error');
    })  #〜追加
省略

失敗した場合の処理で、アラートを表示します。

2
4
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
2
4