chat-spaceというアプリを作成するときajaxを使用した非同期通信を実装したので復習も兼ねて初投稿します。
アプリの非同期通信の見本はこちら
chat-space
非同期通信とは
サーバにHTML形式ではないリクエストを送信し、 HTMLのファイルを読み込まず(ページ遷移を行わず)に、サーバから取得したデータとJavascriptを用いることで、ページの一部分だけを更新することです。
今回はjQueryとRailsとjbuilderを用いたAjaxのコードで解説していきます。
流れ
- chat-spaceでjQueryが使えるように設定し,jQuery を記述するためのファイルを作成する。
- フォームが送信されたら、イベントが発火するようにする。
- 非同期通信でメッセージを保存するようにする。
- respond_toを使用してHTMLとJSON形式で返す。
- jbuilderを使用して、作成したメッセージをJSON形式で返す。
- 返ってきたJSONをdoneメソッドで受け取り、HTMLを作成する。
- 6で作成したHTMLをメッセージ画面の一番下に追加する
- メッセージを送信したとき、メッセージ画面を最下部にスクロールする
- 連続で送信ボタンを押せるようにする
- エラー時の処理を行う。
1. chat-spaceでjQueryが使えるように設定し,jQuery を記述するためのファイルを作成します。
①gem 'jquery-rails'を導入し、bundle installして、chat-space上でjQueryを利用できるようにします。
・Gemfile から turbolinksの部分をコメントアウトし、bundle installを実行します。
turbolinksを停止させましょう turbolinksとはgemとしてRailsアプリケーションに導入されている機能です。 具体的には、手作業でAjaxを導入しなくても、同じような機能を実現してくれる機能です。今回は開発の過程で手作業でAjaxを実装しているので、こちらのturbolinksは削除します。手作業で作成したAjaxとturbolinksが競合してしまい、うまく作動しない可能性があるためです。
# 省略
# Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks
# gem 'turbolinks', '~> 5' # コメントアウトする
# 省略
コメントアウトをしたら、bundle installを実行します。
②application.html.haml から turbolinks の関連部分を削除します。
!!!
%html
%head
%meta{:content => "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}/
%title ChatSpaceSample
= csrf_meta_tags
-# = stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' ← このオプションを消す
-# = javascript_include_tag 'application', 'data-turbolinks-track': 'reload' ← このオプションを消す
= stylesheet_link_tag 'application', media: 'all'
= javascript_include_tag 'application'
%body
= render "layouts/notifications"
= yield
③ application.js から turbolinks の関連部分を削除します。
// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// compiled file. JavaScript code in this file should be added after the last require_* statement.
//
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
// about supported directives.
//
//= require jquery
//= require jquery_ujs
//= require turbolinks ←この記述を消す
//= require_tree .
※上記だけでは、turbolinksを停止できない場合があります。
turbolinksの停止のさせ方は、今までの実装によって異なります。
※turbolinksを削除する方法以外にも、jsファイルでtuborlinksを読み込むことで競合を避ける方法もあります。
④j Queryを記述するためのファイルを作成します。
Ruby on rails内では、JavaScriptファイルをassets/javascripts以下に作成します。
app /assets / javascripts / message.js
2. フォームが送信されたら、イベントが発火するようにする。
$(function(){
//from要素 //submitイベント
$('#new_message').on('submit', function(e){
e.preventDefault()
// console.logを用いてイベント発火しているか確認
})
}
●解説
・フォームの要素を取得して、フォームが送信された時にon()メソッドを使ってイベントが動くようにします。フォームの要素を取得するには、メッセージ送信フォームのid属性をブラウザの検証ツールを使って調べます。
・「on()」は、さまざまなイベント処理を記述するために使われるメソッドになります。一般的な構文としては以下の通りです。
対象要素.on(イベント名,セレクタ,データ,関数)
. event.preventDefauit();
preventDefalt()で送信ボタンによるフォーム送信処理をキャンセルします。
「イベント」とは、分かりやすい例で言うと、フォームのテキストエリアの入力やチェックボックスのチェック、リンクのクリックなどが挙げられます。
preventDefault()でイベントがキャンセルされると、テキストエリアの入力やチェックボックスのチェック、あるいはリンク先への遷移は行われません。
console.logなどを用いて、フォームが送信されたときにイベントが発火しているかどうかを確認しておきましょう
3.非同期通信でメッセージを保存するようにする
フォームに入力されたデータを取得したら、必要なAjax関数のオプションを揃えて非同期通信を行います。
message.jsを以下のように編集します。
$(function(){
$('#new_message').on('submit', function(e) {
e.preventDefault();
var formData = new FormData(this); //formDataを取得
var url = $(this).attr('action');//urlを取得
$.ajax({
url: url, //送信先のurl
type: 'POST', //httpメソッド
data: formData, //コントローラーへ送信するデータ
dataType: 'json', //応答データの種類
processData: false,
contentType: false,
})
})
})
●解説
var formData = new FormData(this);
[・formData]
(https://developer.mozilla.org/ja/docs/Web/API/FormData)
フォーム要素をjavaScriptのオブジェクトにしたもの。
input要素に入力した情報がjavaScriptのオブジェクトとしてキーとバリューとして表されます。
new FormData(フォーム要素)とすることでFormDataを作成できます。
今回FormDataオブジェクトの引数はthisとなっていますが、イベントで設定したfunction内でthisを利用した場合は、イベントが発生したノード要素を指します。今回の場合は、new_messageというIDがついたフォームの情報を取得しています。
var url = $(this).attr('action');
.attrメソッド
「attr()」は、HTML要素の属性を取得したり設定することができるメソッドになります。
述方法としては、【 対象要素.attr( 属性, (変更する値) ) 】のように引数へ任意の属性を指定します。
また、属性を変更する場合のみ第2引数へ変更したい値を指定します。
今回はイベントが発生した要素のaction属性の値を取得しており、今回のaction属性にはフォームの送信先のurlの値が入っています。
これでリクエストを送信する先のURLを定義することができました。
・processDataオプション
dataに指定したオブジェクトをクエリ文字列に変換するかどうかを設定します。初期値はtrueで、自動的に "application/x-www-form-urlencoded" 形式に変換します。DOMDocumentそのものなど、他の形式でデータを送るために自動変換を行いたくない場合はfalseを指定します。
・contentTypeオプション
サーバにデータのファイル形式を伝えるヘッダです。こちらはデフォルトでは「text/xml」でコンテンツタイプをXMLとして返してきます。
ajaxのリクエストがFormDataのときはどちらの値も適切な状態で送ることが可能なため、falseにすることで設定が上書きされることを防ぎます。FormDataをつかってフォームの情報を取得した時には必ずfalseにするという認識で構いません。
Ajax関数のオプションの参考はこちら
4.メッセージを保存し、respond_toを使用してHTMLとJSONの場合で処理を分ける
def create
@message = @group.messages.new(message_params)
if @message.save
respond_to do |format|
format.html {redirect_to group_messages_path, notice: 'メッセージを送信しました'} # HTMLリクエストの場合に呼ばれる
format.json # JSONリクエストの場合に呼ばれる
end
# 一部省略
.respond_to
respond_toを使うとリクエストに含まれているレスポンスのフォーマットを指定する記述を元に条件分岐ができます。
json形式で来たリクエストに対してjson形式のレスポンスを返すための記述を行います。この後、対応するcreate.json.jbuilderを作成することで、レスポンスをjson形式で返すことができます。
5.builderを使用して、作成したメッセージをJSON形式で返すようにする。
. j builder
rails newコマンドでアプリケーションを作成した際にgemfileにデフォルトで記述されているgemで、入力データをJSON形式で出力するテンプレートエンジンです。
jbuilderは、viewと同じように該当するアクションと同じ名前にする必要があります。
messageのcreateアクションに対応するjbuilderのファイルになるので、views/messages以下にcreate.json.jbuilderファイルを作成しす。
app /views / messages/ create.json.jbuilder
create.json.jbuilderのファイルを、決まった文法にそって記述します。JavaScriptで必要なmessageテーブルの情報を渡すようにしましょう。
create.json.jbuilder
json.カラム インスタンス変数.カラム json.カラム インスタンス変数.カラム ...
上記の文法で記述します。
json.user_name @message.user.name
json.created_at @message.created_at.strftime("%Y年%m月%d日 %H時%M分")
json.content @message.content
json.image @message.image_url
これでjbuilderファイルが編集できました。
『リクエストの送信先が正しく設定できているか』と『送信したメッセージのテキストや画像がparamsとしてコントローラで受け取れているか』をbinding.pryで確認しましょう。
参考
[.Pikawaka 【Rails】Pryについて徹底解説!] (https://pikawaka.com/rails/pry)
.pry-byebug 3.7.0
6. 返ってきたJSONをdoneメソッドで受け取り、HTMLを作成する。
非同期通信の結果として返ってくるデータは、done(function(引数) { 処理 })の関数の引数で受け取ります。
この引数の値を元に、HTMLを組み立てます。
function buildHTML(message) {
//条件分岐
var image = message.image ?`<img src= "${message.image}">`:"";
//messageのhtml
var html = `<div class ="message" data-message-id="${message.id}">
<div class ="upper-message">
<div class ="upper-message__user-name">
${message.user_name}
</div>
<div class ="upper-message__date">
${message.date}
</div>
</div>
<div class ="text-message">
<p class ="text-message__content">
${content}
</p>
${image}
</div>
</div>`
return html;
}
$('#new_message').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){ //返ってきたJSONを受け取る
var html = buildHTML(data);
})
})
});
●解説
doneメソッドで受け取ったjsonのdataをそのままbuildHTMLメソッドに渡し、その返り値として完成したHTMLの塊を受け取っています。
HTMLを組み立てる処理は以下のようなメソッドとして定義しましょう。条件(三項)演算子を使って画像がある場合とない場合で条件分岐して記述します。
JavaScriptの条件(三項)演算子は条件式? tureの処理 : falseの処理と記述します。
【JavaScript入門】条件(三項)演算子の使い方と活用例まとめ!
また、追加したhtmlはテンプレートリテラル記法で記述します。
・テンプレートリテラル記法
ダブルクオートやシングルクオートの代わりにバックティック文字で囲むことで、複数行文字列や文字列内挿入機能を使用できます。
buildHTMLの引数として渡されたmessageはサーバから返されたデータであるjbuilderのデータであるため、ファイル内で定義したキーとバリューの形式で使用することができます。
7. 6で作成したHTMLをメッセージ画面の一番下に追加する
.done(function(data){
var html = buildHTML(data);
$('.messages').append(html); //受け取ったHTMLを'.messages'クラスの一番最後に追加する
$('form')[0].reset();//formを空にする
})
.appendメゾット 対象の要素の末尾にテキストやHTML要素を追加するメソッド
受け取ったHTMLを、appendメソッドによって.messagesというクラスが適用されているdiv要素の子要素の一番最後に追加します。また、フォームを空にする処理も書きます。
8.メッセージを送信したとき、メッセージ画面を最下部にスクロールする
メッセージが溜まってきて画面いっぱいになった時、メッセージが入っているdiv要素に
overflow: scroll;プロパティが指定できていれば、縦にスクロールできます。
$('.messages').animate({ scrollTop: $('.messages')[0].scrollHeight});// ページ先頭の位置に移動
.animateメソッド
アニメーションを実装するためのメソッドで、オブジェクトの移動や、透過率などを変更することができます。
書き方は以下のようになります。
$(function() {
$(‘動かす要素名’).animate({
‘動かすアニメーション名’: ‘動かす距離’
});
});
.scrollTop
「scrollTop()」は、ブラウザの画面をスクロールした時の位置(スクロール量)を取得できるメソッドです。
()の中に座標を指定することで任意の場所に飛ぶことができます。
.scrollHeight
overflowした画面上に表示されていないコンテンツを含む要素の内容の高さを表します。
どこの要素の高さを取得しているのかというと、$('.messages')[0]です。
メッセージクラスから生成されるjQueryオブジェクトは配列のように扱うことができるので、[0]と指定することで1番下の要素を取得します。
9. 連続で送信ボタンを押せるようにする
message.jsにこちらの記述を追加します。
$(".form__submit").prop("disabled", false);
buttonタグなどのdisabled属性の切り替えを、jQueryのprop()によって切り替えます。
htmlの仕様でsubmitボタンを一度押したらdisabled属性というボタンが押せない属性が追加されます。
そのため、disabled属性をfalseにすることでdisabledが外れてボタンが有効になります。
10. エラー時の処理を行う。
.fail(function() {
alert ('メッセージ送信に失敗しました');
});
サーバーエラーの場合、failの関数が呼ばれます。
alert()メソッドとはアラートを出すメソッドです。
alert( 画面に表示させたい値 )
上記のように引数へ値を指定するだけで、任意の値をポップアップ表示させることができます。この値は、文字列や数値などさまざまな値を指定することが可能です。
これで非同期通信の実装が完成しました。
最後に完成コードを載せます。
function buildHTML(message) {
var image = message.image ?`<img src= "${message.image}">`:"";
var html = `<div class ="message" data-message-id="${message.id}">
<div class ="upper-message">
<div class ="upper-message__user-name">
${message.user_name}
</div>
<div class ="upper-message__date">
${message.date}
</div>
</div>
<div class ="text-message">
<p class ="text-message__content">
${content}
</p>
${image}
</div>
</div>`
return html;
}
$('#new_message').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);
$('.messages').append(html);
$("form")[0].reset();
$('input').prop('disabled', false);
$('.messages').animate({scrollTop: $('.messages')[0].scrollHeight});
})
.fail(function() {
alert("メッセージ送信に失敗しました");
});
});
# 一部省略
def create
@message = @group.messages.new(message_params)
if @message.save
respond_to do |format|
format.html { redirect_to group_messages_path(@group), notice: 'メッセージが送信されました' }
format.json
end
else
@messages = @group.messages.includes(:user)
flash.now[:alert] = 'メッセージを入力してください。'
render :index
end
end
# 一部省略
json.user_name @message.user.name
json.created_at @message.created_at.strftime("%Y年%m月%d日 %H時%M分")
json.content @message.content
json.image @message.image_url