プログラミング初学者の自分にとって非同期通信の流れというかイメージがなかなか出来なかったのが実装をするまでの苦労でした。
・ajaxを使って飛ばしたデータはどこで受け取ってるの?
・受け取ったデータはどう処理してるの?
・処理したデータをどうやってviewに反映させるの? etc...
全く掴めていなかったイメージがある程度ついたのでアウトプットとしてメモしていこうと思います。
※間違った知識を伝えていたらご指摘ください!
環境
Ruby 2.3.1
Rails 5.0.7.2
JavascriptのライブラリであるjQueryを用いて非同期機能を実装していきます!
※フォームの作成、コントローラーの作成が予め出来ている設定で進めていきます。
###メッセージ送信機能の流れ
① jQueryを記述するためのファイルxxx.jsを作成
② フォームが送信されたらイベントが発火するようにjsファイルにコードを書く
③ イベントが発火した時にajaxを使用してcreateアクションが動くようにする
④ createアクションでメッセージを保存し(非同期通信でメッセージが保存される)respond_toを使用してHTMLとJSONの場合で処理を分ける
⑤ jbuilderを使用して送信したメッセージをJSON形式で返す
⑥ 返ってきたJSONをdoneメソッドで受け取りjsファイル内でHTMLを作成する
⑦ 作成したHTMLをメッセージ画面に追加する
⑧ メッセージを連続で送信できるようにする
⑨ HTMLを追加した分メッセージ画面を下にスクロールする
ざっとした流れは以上になります。
非同期通信は出来たけど**一回ページを読み込まないと上手く行かない!という人やメッセージを連続で送ろうとしても送信ボタンが反応しない!**という人に向けても対応方法が書いてあります!
##①,②
①の手順はファイルを作成するだけです。
ファイル名は任意ですがファイルを見返した時になんの機能を実装しているかが分かりやすいような命名にしましょう。
②フォームが送信されたらイベントが発火されるように設定する
送信されたらイベントを発火させたいのでclickでなくsubmitを使ったほうがいいですね。
e.preventDefault();
これはメッセージを非同期通信
にさせたいのですがデフォルトだとフォーム送信ボタンが押されるとフォームを送信するための通信が行われるためそのイベントをキャンセルさせるために書いています。
「e=event」でpreventは妨げるという意味になります。
Rails5を使っている方はturbolinksが悪さをしていることがあるので$(document).ready(function(){})
ではなくturbolinksを初回読み込み、リロード、ページ切り替えで動くように上のように記述しましょう。
そうすることによってページを一回読み込まないと上手くいかないという事がなくなるはずです。
$(function(){}) = $(document).ready(function(){})
です。
$(document).on('turbolinks:load', function(){
$('#new_message').on('submit', function(e){
e.preventDefault();
var message = new FormData(this); //フォームに入力した値を取得しています。
})
});
console.logを使ってフォームが送信された時にイベントが発火しているか確認してみましょう!
console.log(message);として値が取れていればしっかりと発火されています。
##③Ajaxを使用してcreateアクションを動かす
イベントの発火が確認できたらajaxを使用してデータを送ります。
jQueryの関数である$.ajax()を使うと簡単にHTTPリクエスト(GET,POSTなど)を発行する事ができます。
③から⑦までの処理をざっくりと書くと以下のようになります
- Javascriptからリクエストを飛ばす
- Javascriptでレスポンスを受け取る
- JavascriptでDOMを書き換える
$(document).on('turbolinks:load', function(){
$('#new_message').on('submit', function(e){
e.preventDefault();
var message = new FormData(this);
var url = (window.location.href); // $(this).attr('action')でも可能です
$.ajax({
url: url,
type: 'POST',
data: message,
dataType: 'json',
processData: false,
contentType: false
})
})
});
urlはPOSTリクエストを送りたいルーティングをrake routesコマンドで確認してから変数にするといいと思います。
私の場合はmessagesコントローラーのcreateアクションを動かしたかったので上にあるようなURLの取り方をしています。
送りたいデータは変数のmessageなのでこれをdataとします。
ajaxを使って同期する事なくPOSTリクエストがサーバーに送られ、URLを指定したのでルーティングによりmessagesコントローラーのcreateアクションを動かす事が出来るはずです。
##④createアクションでメッセージを保存し処理を分ける。
③からの流れでcreateアクションを動かす事ができておりajaxから正しくdataが送れていればメッセージを保存する事が可能になります。
respond_toでHTMLとJSONの場合で処理を分けています。
$.ajaxのオプションでdataTypeをjsonに指定しているので非同期通信でjson形式のデータが送られてきます。
受けた値は⑤でjbuilderを使用して作成したメッセージをJSONで返します。
def create
@message = @group.messages.new(message_params)
if @message.save
respond_to do |format|
format.html { redirect_to "group_messages_path(params[:group_id])" }
format.json
end
else
//
end
end
##⑤jbuilderを使用して送信したメッセージをJSON形式で返す
respond_toで処理を分けたのでjbuilderを使用して返すデータを作成していきます。jbuilderはデフォルトで記述されているgemで入力されたデータをJSON形式で出力します。
jbuilderのファイルを作成していきますが今回はmessagesコントローラのcreateアクションに対応するjbuilderファイルになるのでviews/messages/create.json.jbuilderとしてファイルを作成します。
json.id @message.id
json.content @message.content
json.date @message.created_at.strftime("%Y/%m/%d %H:%M")
json.user_name @message.user.name
json.image @message.image.url
jsonファイルでは基本的にjson.KEY VALUEという形で書きます。
こうすることによりjsファイルに返ってきたデータをjbuilderで定義したキーとバリューの形で呼び出して使う事ができます。
##⑥,⑦返ってきたjsonデータをdone()で受け取りHTMLを作成する。作成したHTMLをメッセージ画面に追加する。
非同期通信の結果として返ってくるjbuilerで出力されたjson形式のデータはdone(function(data){処理})の関数の引数で受け取ります。
私はファイルの中でdataとしていますがここはmessageであったり任意の文字列でもデータを受け取ることは出来ます。
この受け取った値を元にHTMLを組み立てていきます。
done関数の中にHTMLを作成することも可能ですがコードの量が多くなってしまい見づらくなってしまうので別で定義して変数として呼び出しています。
HTMLの追加ではテンプレートリテラル記法を使用しています。
引用
テンプレートリテラル記法
done関数の中で変数htmlを定義しappendメソッドを使い作成したHTMLを追加しています。
$('#message_content').val('');ではinput内の値を消しています。
$(document).on('turbolinks:load', function(){
function buildHTML(message) {
var content = message.content ? `${ message.content }` : "";
var img = message.image ? `<img src= ${ message.image }>` : "";
var html = `<div class="message" data-id="${message.id}">
<div class="message__detail">
<p class="message__detail__current-user-name">
${message.user_name}
</p>
<p class="message__detail__date">
${message.date}
</p>
</div>
<p class="message_body">
<div>
${content}
</div>
${img}
</p>
</div>`
return html;
}
$('#new_message').on('submit', function(e){
e.preventDefault();
var message = new FormData(this);
var url = (window.location.href);
$.ajax({
url: url,
type: 'POST',
data: message,
dataType: 'json',
processData: false,
contentType: false
})
.done(function(data){
var html = buildHTML(data);
$('.messages').append(html);
$('#message_content').val(''); //input内のメッセージを消しています。
})
.fail(function(data){
alert('エラーが発生したためメッセージは送信できませんでした。');
})
})
});
##⑧メッセージを連続で送信出来るようにする
今のままでは一回メッセージを送信するとSendボタンを入力してもボタンがが反応していないと思います。
下のGIFを見てもらうとメッセージを送信後にdisabledが追加されているのが確認できます。
disabledが追加されるとボタンが無効化されてしまいます。
これを解除するためのメソッドを記述をする必要がありそうです。
$('#new_message').on('submit', function(e){
e.preventDefault();
var message = new FormData(this);
var url = (window.location.href);
$.ajax({
url: url,
type: 'POST',
data: message,
dataType: 'json',
processData: false,
contentType: false
})
.done(function(data){
var html = buildHTML(data);
$('.messages').append(html);
$('#message_content').val('');
scrollBottom();
})
.fail(function(data){
alert('エラーが発生したためメッセージは送信できませんでした。');
})
.always(function(data){
$('.submit-btn').prop('disabled', false); //ここで解除している
})
})
解除する方法はいくつかあると思いますが私はpropメソッドを使いdisabledをfalseにしました。
メッセージ送信を失敗した場合でもボタンが固まらないようにalwaysを使っています。
##⑨HTMLを追加した分メッセージ画面を下にスクロールする
スクロールするにはanimateメソッドを使えばすぐに実装できます。
非同期通信とはあまり関係がないので詳しい説明は割愛させていただきます。
function scrollBottom(){
var target = $('.message').last();
var position = target.offset().top + $('.messages').scrollTop();
$('.messages').animate({
scrollTop: position
}, 300, 'swing');
}
この関数をdone()の処理の中に入れればスクロールがされるはずです。
###以上となります!
途中から疲れてしまいわかりづらく伝えている部分があるかもしれないので修正していこうかと思っています。。
文字数自体は多くないと思うのですが自分が何が分かっていなかったのか等を振り返りながら書いていると思ったよりも時間がかかってしまいました。
ご覧いただきありがとうございました!