某スクールにおいて、チーム開発で、フリーマーケットアプリを作成中であり、使用した技術について公開しています。
※初学者のため、ミスや認識違いが多々あると思いますがご了承ください。
商品詳細ページにコメント機能を実装しました。
全部で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を用いて実装する部分は、**「コメント作成」「コメント復元」**した場合となります。「コメント作成」「コメント復元」については、非同期通信を用いてデーターベースにアクセスし、受け取ったデーターを元に、画面上の表示の一部を作り替えて変更します。
まずは**「コメント作成」**について説明したします。
動作イメージ
1. 「コメントする」をクリックしたら、jqueryが発火
2. 入力されたデーターを非同期でサーバーに送り、json情報を受け取る。
3. 受け取った情報を元に条件をif文でふるいにかけてhtmlを形成する。
4. 作成作成した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(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アクションでの処理をみていきます。
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形式でデーターが返されます。
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形式でデーターを作成することができます。
 .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に格納します。
// ===================================
// 新規コメント表示用・自分のコメント復元用 
// ===================================
  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-list").append(html)
    $('#comment_body').val("");
    $('.comment_list').animate({ scrollTop: $('.comment_list')[0].scrollHeight});
帰ってきた変数htmlをコメントの一覧に追加して、コメントフォームの中身(入力された文字)を消去して、画面を新しく追加したコメントの位置までスクロールします。
これで、非同期通信を用いた**「コメント作成」機能**が実装できました!!
最後に**「コメント復元」**について説明したします。
動作イメージ
以下が、「復元するボタン」をクリックした際に、発動するプログラムとなります。
// ===================================
// 復元した場合
// ===================================
$(".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-list").on('click',".comment-restore",function(e){
  e.preventDefault()
    var index = $(this).data("index")
    var url =`/comments/${index}/restore`
「復元ボタン」がクリックされたら発火します。
発火元の「復元ボタン」に与えられたカスタム属性値(コメントのid)が、変数indexに格納されます。
変数urlには、pathの中にindexが埋め込まれた形で格納されます。
    $.ajax({
    url: url,
    type: "get",
    dataType: 'json',
  })
commentsコントローラーのupdateアクションに返却値をjson形式でリクエストし、データーが送られます。
  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形式にして返却する。
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
.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と構造が同じであるため、同じコードを流用できるため。
  if (comment_data.item_seller.id == comment_data.user_id){   // 出品者とコメントユーザーが同じ場合
  var html = new_comment(comment_data); //コメント作成時と同じメソッドを呼び出している。
    $(`.comment-one-block[data-index=${index}]`).replaceWith(html)
「復元ボタン」と同じカスタムデーター属性値を持つコメントを取得し(つまり復元ボタンが所属するコメント)、中身をreplaceWithメソッドで返り値のhtmlで置き換えている。
 }else{    // 出品者とコメントユーザーが異なる場合
      var html = restore_other_comment(comment_data);
      $(`.comment_one_block[data-index=${index}]`).replaceWith(html)
    }
// ===================================
// 他人のコメント復元用 
// ===================================
  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;
  };



