##Gemfileの記述
Gemfileにjquery-railsが記載されているか確認し、記載されていない場合は最下部に記載してください。
〜省略〜
# Use jquery as the JavaScript library
gem 'jquery-rails'
〜省略〜
記述したときはターミナルで
bundle install
##application.jsの記述を確認
application.jsに
//= require jquery
//= require rails-ujs
の記載がされているか確認します。なければ以下のように記述してください。
〜省略〜
//= require jquery
//= require rails-ujs
//= require_tree .
必要なクラス名とID
ajaxの非同期通信を実装するために、事前にコメントのフォームにクラス名を与えておきます。
仮に
new_commentというid
textboxというclass
form__submitというclassをつけるとします。
【例】モデル名をTweetとします。
<%= 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に、コメントフォームが送信された時フォームに入力された値を受け取れるようにします。
【例】
$(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がついたフォームの情報を取得しています。
##フォームの値を確認
$(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)は削除します。
$(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が同期通信です。それを非同期通信の記述に編集します。
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
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があるとします。
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) { 処理 })の関数の引数で受け取ります。
$(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のデータであるため、ファイル内で定義したキーとバリューの形式で使用できます。
##エラー時の処理を行う
〜省略〜
.done(function(data){
var html = buildHTML(data);
$('.comments').append(html);
$('.textbox').val('');
$('.form__submit').prop('disabled', false);
})
.fail(function(){ #追加〜
alert('error');
}) #〜追加
〜省略〜
失敗した場合の処理で、アラートを表示します。