メル○リのようなフリマアプリ制作にて、商品に対してコメント投稿後に非同期で即時にブラウザに反映されるように実装を行いました。その手順について解説します。コメント投稿の度にリロードが走る同期通信の実装は完了しているものとします。
完成画像
実装手順
以下の手順で実装していきます。
❶ 事前準備
❷ フォームが送信されたら、イベント発火するようにする
❸ 非同期でコメントが保存されるようにする
❹ respond_toを使用してHTMLとJSONの場合で処理を分ける
❺ jbuilderを使用して、作成したメッセージをJSON形式で返す
❻ 返ってきたJSONをdoneメソッドで受け取り、HTMLを作成する
❼ エラー時の処理を行う
❶ 事前準備
Gemにjquery-railsをインストール
gem 'jquery-rails'
application.jsの記述を確認
下記2行が記述されていれば正になります。
//= require jquery
//= require jquery_ujs
必要なクラス名とIDを与える
ajaxによる非同期通信に必要な以下のクラス名、IDをコメントフォームのビューに与えておきます。
id : new_comment
class : textbox
class : form__submit
app/views/items/show.html.haml
.itemcomment__box__content
- if current_user
= form_with(model:[@item, @comment], class: "message-form", id: "new_comment") do |form|
%p 相手のことを考え丁寧なコメントを心がけましょう。不快な言葉遣いなどは利用制限や退会処分となることがあります。
= form.text_area :text, {class: "textbox"}
%input{type:"submit", class:"form__submit", value:"コメントする"}
- else
%strong
%p ※※※ コメントの投稿には新規登録/ログインが必要です ※※※
.itemcomment__box__content__comment
%p < コメント一覧 >
- if @comments
.itemcomment__box__content__comment__member
- @comments.each do |comment|
.itemcomment__box__content__comment__member__list
= comment.user.nickname
%strong :
= comment.text
❷ フォームが送信されたら、イベント発火するようにする
jQueryを記述するためのファイルcomment.jsを作成します。コメント投稿ボタンを押したときにイベントが発火するようにします。
app/assets/javascripts/comment.js
$(function(){
$('#new_comment').on('submit', function(e){
e.preventDefault();
var formData = new FormData(this);
})
})
・ form要素のid属性「new_comment」に対して、onメソッドでイベントを設定します
・ フォーム送信時のデフォルト動作はキャンセルします「e.preventDefault()」
・ FormDataでフォームのデータを取得します
❸ 非同期でコメントが保存されるようにする
フォームの送信が行われたときに、Ajaxによる非同期通信でcommentコントローラのcreateアクションを実行します。
app/assets/javascripts/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
})
})
})
formDataから取得したフォームの入力データからObjectを作成してthisに代入します。
thisの送り先をurlで指定します。attrで指定した要素のaction属性の値を返します。
❹ コメントを保存して、respond_toを使用してHTMLとJSONの場合で処理を分ける
app/controllers/comments_controller.rb
class CommentsController < ApplicationController
def create
@item = Item.find(params[:item_id])
@comment = @item.comments.build(comment_params)
@comment = Comment.new(comment_params)
if @comment.save
respond_to do |format|
format.html {redirect_to item_path(@item)}
format.json
end
else
flash.now[:alert] = 'コメントを入力してください。'
end
end
ローカル変数はこの後のjbuilderで使用できないので、インスタンス変数@commentを使用します。
❺ jbuilderを使用して、作成したメッセージをJSON形式で返す
app/views/comments/create.json.jbuilder
json.text @comment.text
json.user_id @comment.user.id
json.user_name @comment.user.nickname
ここでuser_nameを定義していなかったので、非同期通信ではユーザ名がundefinedになりました。
❻ 返ってきたJSONをdoneメソッドで受け取り、HTMLを作成する
app/assets/javascripts/comment.js
$(function (){
function buildHTML(comment){
var html =
`<div class="itemcomment__box__content__comment__member__list">
${comment.user_name}
:
${comment.text}
</div>`
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);
$('.itemcomment__box__content__comment__member').prepend(html)
$('.textbox').val('');
$('.form__submit').prop('disabled', false);
})
.fail(function(){
alert('error');
})
})
})
doneメソッドで非同期通信に成功したパターンを記述します。
buildHTMLで非同期でコメント一覧に追加していくコメントのHTMLを作成します。
2行目からのfunction buildHTMLでは実際のDOMと同じ構成にして、追加するコメントのテンプレートを作成します。(テンプレートリテラル記法)
prependで追加していくクラス名も実際のビューと同じ名前にします。
❼ エラー時の処理を行う
app/assets/javascripts/comment.js
.fail(function(){
alert('error');
})