3
3

More than 3 years have passed since last update.

【フリマアプリ】商品詳細ページのコメント機能(第6回)〜ajax編〜

Last updated at Posted at 2020-05-02

 某スクールにおいて、チーム開発で、フリーマーケットアプリを作成中であり、使用した技術について公開しています。
※初学者のため、ミスや認識違いが多々あると思いますがご了承ください。

商品詳細ページにコメント機能を実装しました。

全部で7回に分けて記事を投稿しています。

内容 url
第1回 モデル、マイグレーション編 https://qiita.com/sho_U/items/03108801146e65d58413
第2回 ルーティング編 https://qiita.com/sho_U/items/5c829b3060be2cce919a
第3回 コントローラー編 https://qiita.com/sho_U/items/8528f336cf0d470cd719
第4.1回 ヴュー編(一覧表示) https://qiita.com/sho_U/items/6190562270c722956547
第4.2回 ヴュー編(インプットフォーム) https://qiita.com/sho_U/items/67c2ede4fc6c605283e2
第5回 jquery編 https://qiita.com/sho_U/items/d72b60114f76380d05f6
第6回 ajax編 https://qiita.com/sho_U/items/caed9b1471e63d43dd3a

〜コード全文〜https://qiita.com/sho_U/items/310ac5b653bdfcb99a2c

ajax(非同期通信について)

ajaxを用いて実装する部分は、「コメント作成」「コメント復元」した場合となります。「コメント作成」「コメント復元」については、非同期通信を用いてデーターベースにアクセスし、受け取ったデーターを元に、画面上の表示の一部を作り替えて変更します。

まずは「コメント作成」について説明したします。

動作イメージ

3aaeca9592fab557cf4cd0b34b041388.gif

1. 「コメントする」をクリックしたら、jqueryが発火
2. 入力されたデーターを非同期でサーバーに送り、json情報を受け取る。
3. 受け取った情報を元に条件をif文でふるいにかけてhtmlを形成する。
4. 作成作成したhtmlをコメント一覧に追加する。

という流れになります。

以下が、コメントを追加するボタンをクリックした際に、発動するプログラムとなります。

comment.js
// ===================================
// コメント作成した場合
// ===================================
  $('.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(comment_data){
    var html = new_comment(comment_data);
    $(".comment_list").append(html)
    $('#comment_body').val("");
    $('.comment_list').animate({ scrollTop: $('.comment_list')[0].scrollHeight});
  })
  .fail(function() {
    alert("メッセージ送信に失敗しました");
  });
});

解説します

$('.new_comment').on('submit', function(e){
 e.preventDefault()

onメソッドで、第一引数にsubmitイベントを指定し、フォームデーターが送信されたら発火して、まずブラウザのデフォルトの動作を停止します。

    var formData = new FormData(this);

イベント発火元の情報("this")を引数にFromDataを作成し、変数formDataに格納します。
ちなみにFormDataとは、読み込んだブラウザ上から、さらにHttp通信リクエストを行うためのAPIであるXMLHttpRequestを使用して、送信するためのキーと値のペアのセットを収集可能にします。

FormDataについて(参考)
https://qiita.com/akkt222/items/ce93d53e8d92a78a0694
XMLHttpRequestについて(参考)
https://ja.javascript.info/xmlhttprequest
XMLについて(参考)
https://www.cybertech.co.jp/xml/contents/xmlxmldb/serial/_xmlbeginner2.php

var url = $(this).attr('action');

attrメソッドで、発火元フォームのaction属性の値("/comments")を取得し変数urlに格納しています。

ちなみにフォームタグの属性は以下のようになっています。
<form class="new_comment" action="/comments" accept-charset="UTF-8" method="post">
$.ajax({
      url: url,
      type: "POST",
      data: formData,
      dataType: 'json',
      processData: false,
      contentType: false
    })

ajax通信を行います。
url: は、先ほどの変数urlに入っている値、つまり "/comments" 。
type: でHTTPのリクエストメソッドをPOSTに指定。これにより、commentsコントローラーのcreateアクションを送信先に指定します。
dataType: で受け取るデーターのタイプを jsonに指定します。

processData: false
contentType: false

data:
サーバーへ送信するデータです。 もし文字列で無ければ、クエリー文字列に変換されます。 これはGETリクエストのURLに追加されます。 この自動的に行われるプロセスを防ぎたい場合は、processDataのオプションを参照してください。

processData:
デフォルトでは、dataオプションにオブジェクトとして渡されるデータ(厳密に言えば、文字列以外のもの)は、 デフォルトのcontent-typeである"application/x-www-form-urlencoded"に合わせた形式でクエリー文字列へ変換されます。 もしDOMDocument、またはその他の形式のデータを送信したい場合は、このオプションをfalseに設定します。
参考:https://nodoame.net/archives/7215

contentType:はデータ送信時のcontent-typeヘッダの値になりますが、FormDataオブジェクトの場合は適切なcontentTypeが設定されるので、同じくfalseを設定します。
参考:http://www.koikikukan.com/archives/2014/09/30-013333.php

今回は、httpメソッドは”post"であり、クエリー文字列に変換する必要はないためfalseとなる。

続いては、データーが送られてきたcommentsコントローラーのcreateアクションでの処理をみていきます。

commnets_controller.rb
def create
  @comment = Comment.new(comment_params)
  @seller_of_item = @comment.item.seller
  if @comment.save
     respond_to do |format|
       format.json
     end
  else
    flash[:alert] = "保存できていません"
    redirect_to item_path(params[:id])
  end
end

private
  def comment_params
    params.require(:comment).permit(:comment,:item_id).merge(user_id: current_user.id)
  end
end

解説します。
@commnetsに、フォームから送られてきたデーターを、@seller_of_itemには出品者を格納しています。
(詳細は、第3回〜コントローラー編〜https://qiita.com/sho_U/items/8528f336cf0d470cd719)

if @comment.save
     respond_to do |format|
       format.json
     end

@commnetのsaveが正常に行われた場合、respond_toメソッドにより、json形式でリクエストされた場合は、jbuilderを通じて、json形式でデーターが返されます。

views/comments/create.json.jbuilder
json.comment @comment.comment
json.id @comment.id
json.user_nickname @comment.user.nickname
json.user_id @comment.user.id
json.created_at @comment.created_at.strftime("%Y年%m月%d日 %H時%M分")
json.item_seller @seller_of_item

jbuilderは

json.<キー> <値>
=> {"キー": "値"}

とすることで簡単に、json形式でデーターを作成することができます。

comment.js
 .done(function(comment_data){
   var html = new_comment(comment_data);

通信が成功した場合は、返されたjsonを、comment_dataとして引数に格納します。
検証で、comment_dataの中身を確認すると

comment_data:
  comment: "お安く提供します。"
  created_at: "2020年05月02日 00時52分"
  id: 21
  item_seller:
    avatar: null
    birthday: "2018-06-06"
    created_at: "2020-04-04T14:55:54.000+09:00"
    email: "2@gmail.com"
    family_name: "高木"
    family_name_reading: "タカギ"
    first_name: "ぶー"
    first_name_reading: "ブー"
    id: 2
    nickname: "ブー"
    password: null
    phone_number: "08022222222"
    self_introduction: null
  updated_at: "2020-04-04T14:55:54.000+09:00"
  __proto__: Object
  user_id: 2
  user_nickname: "ブー"

json形式でコントローラーから送られたデーターが格納されていることがわかります。

このcomment_dataを引数に、new_commentメソッドを呼び出しています。
呼び出した返り値を変数htmlに格納します。

comment.js
// ===================================
// 新規コメント表示用・自分のコメント復元用 
// ===================================
  function new_comment(comment_data){
    var HTML_content_time = 
      `
      <div class="comment_Me comment_one_block" data-index=${comment_data.id}>
        <div class="comment_content">
          ${comment_data.comment}
          <div class="comment_create_at">
            ${comment_data.created_at}
          </div>
      `
    var HTML_deleteBtn =  
        `
        <div class="comment_delete me_pre_delete" data-index=${comment_data.id}>
          <a rel="nofollow" data-method="patch" href="/comments/${comment_data.id}">削除する</a>
        </div>
        `
    var HTML_nickname =
        `
        </div>
        <div class="comment_user_name">
          ${comment_data.user_nickname}
        `
    var HTML_sellerMark =
        `
          <div class="seller_display">
          出品者
          </div>
        `
    var HTML_endDiv =
      `
        </div>
      </div> 
      `
    if (comment_data.item_seller.id == comment_data.user_id){
        // 出品者とコメントしたユーザーが等しい場合
      var html = HTML_content_time + HTML_deleteBtn + HTML_nickname + HTML_sellerMark + HTML_endDiv
    }else{
       // 出品者とコメントしたユーザーが異なる場合
      var html = HTML_content_time + HTML_nickname + HTML_endDiv
          };

    return html;
  };

追加するコメントを、部分ごとに分けて作成し、出品者がコメントしたのか、出品者以外がコメントしたのかでif文で場合分けして、変数htmlを組み立てています。(出品者のコメントには、削除ボタン出品者マークが表示される)

comment.js
    $(".comment-list").append(html)
    $('#comment_body').val("");
    $('.comment_list').animate({ scrollTop: $('.comment_list')[0].scrollHeight});

帰ってきた変数htmlをコメントの一覧に追加して、コメントフォームの中身(入力された文字)を消去して、画面を新しく追加したコメントの位置までスクロールします。

これで、非同期通信を用いた「コメント作成」機能が実装できました!!

最後に「コメント復元」について説明したします。

動作イメージ

      自分のコメントを復元
865a81ee7b88e763a74a94316ed88e45.gif

      他人のコメントを復元
8e0e551b6c2f240e395c4048326d6d09.gif

以下が、「復元するボタン」をクリックした際に、発動するプログラムとなります。

comment.js
// ===================================
// 復元した場合
// ===================================
$(".comment-list").on('click',".comment-restore",function(e){
  e.preventDefault()
  var index = $(this).data("index")
  var url =`/comments/${index}/restore`
  $.ajax({
    url: url,
    type: "get",
    dataType: 'json',
  })
  .done(function(comment_data){
    if (comment_data.item_seller.id == comment_data.user_id){   // 出品者とコメントユーザーが同じ場合
      var html = new_comment(comment_data);
      $(`.comment-one-block[data-index=${index}]`).replaceWith(html)
    }else{    // 出品者とコメントユーザーが異なる場合
      var html = restore_other_comment(comment_data);
      $(`.comment-one-block[data-index=${index}]`).replaceWith(html)
    }
  })
  .fail(function() {
    alert("メッセージ送信に失敗しました");
  });
});

解説します。

comment.js
$(".comment-list").on('click',".comment-restore",function(e){
  e.preventDefault()
    var index = $(this).data("index")
    var url =`/comments/${index}/restore`

「復元ボタン」がクリックされたら発火します。
発火元の「復元ボタン」に与えられたカスタム属性値(コメントのid)が、変数indexに格納されます。
変数urlには、pathの中にindexが埋め込まれた形で格納されます。

comment.js
    $.ajax({
    url: url,
    type: "get",
    dataType: 'json',
  })

commentsコントローラーupdateアクションに返却値をjson形式でリクエストし、データーが送られます。

commnets_controller.rb
  def restore
    @comment.update(delete_check:0)
    @seller_of_item = @comment.item.seller
    respond_to do |format|
    format.json
  end

  def set_comment
    @comment = Comment.find(params[:id])
  end

送られてきたparams[:id]で、復元すべきコメントを特定し、@commnetに格納し、delete_checkを"0"にして「仮削除状態」を解除して更新する。
jbuilderを呼び出し、json形式にして返却する。

views/comments/restore.json.jbuilder
json.comment @comment.comment
json.id @comment.id
json.user_nickname @comment.user.nickname
json.user_id @comment.user.id
json.created_at @comment.created_at.strftime("%Y年%m月%d日 %H時%M分")
json.item_seller @seller_of_item
comment.js
.done(function(comment_data){
    if (comment_data.item_seller.id == comment_data.user_id){   // 出品者とコメントユーザーが同じ場合
      var html = new_comment(comment_data);
      $(`.comment-one-block[data-index=${index}]`).replaceWith(html)
    }else{    // 出品者とコメントユーザーが異なる場合
      var html = restore_other_comment(comment_data);
      $(`.comment-one-block[data-index=${index}]`).replaceWith(html)
    }

「出品者自身のコメント」と、「その他のユーザーのコメント」で場合分けしている。理由は、出品者自身のコメントを復元した場合は、コメント作成時に追加するhtmlと構造が同じであるため、同じコードを流用できるため。

comment.js
  if (comment_data.item_seller.id == comment_data.user_id){   // 出品者とコメントユーザーが同じ場合
  var html = new_comment(comment_data); //コメント作成時と同じメソッドを呼び出している。
comment.js
    $(`.comment-one-block[data-index=${index}]`).replaceWith(html)

「復元ボタン」と同じカスタムデーター属性値を持つコメントを取得し(つまり復元ボタンが所属するコメント)、中身をreplaceWithメソッドで返り値のhtmlで置き換えている。

comment.js
 }else{    // 出品者とコメントユーザーが異なる場合
      var html = restore_other_comment(comment_data);
      $(`.comment_one_block[data-index=${index}]`).replaceWith(html)
    }
comment.js
// ===================================
// 他人のコメント復元用 
// ===================================
  function restore_other_comment(comment_data){
    var html = 
    `
    <div class="comment_Other comment_one_block" data-index=${comment_data.id}>
      <div class="comment_user_name">
      ${comment_data.user_nickname}
      </div>

      <div class="comment_content_other">
        ${comment_data.comment}
        <div class="comment_create_at">
          ${comment_data.created_at}
        </div>
        <div class="comment_delete other_pre_delete" data-index=${comment_data.id}>
          <a rel="nofollow" data-method="patch" href="/comments/${comment_data.id}">削除する</a>
        </div>
      </div>
    </div>
    `
  return html;
  };

これで、コメント機能における全ての実装が完了しました!!

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3