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

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

```Gemfile
〜省略〜
# 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');
    })  #〜追加
〜省略〜
```
失敗した場合の処理で、アラートを表示します。