hiyoko-programingの日記

プログラミングを勉強したてのひよっ子。   エンジニア目指して勉強中。

pictweetでコメント機能を非同期通信

作成する機能を把握する

今回作成する機能は以下の画像のような機能。
いままでのpictweetのコメント送信機能と異なるのはajaxを使って非同期通信で行っている点である。

https://gyazo.com/8025313e3f3b7fbbb5ab5fcff71abd28

作業ディレクトリを以前に完成させたpictweetに移動

ターミナル
1
2
3
$ cd  #ホームディレクトリに移動
$ cd projects #projectsディレクトリに移動(projectsとは異なるディレクトリにpictweetを配置している場合は任意のディレクトリに移動)
$ cd pictweet #pictweetディレクトリに移動

 pictweetをテキストエディタで開く

完成しているものでない場合、この後の作業しても正しく動かないことがあるので、必ず完成しているものを選択する。

 Gemfileの記述を確認

Gemfileにjquery-railsが記載されているか確認。

pictweet/Gemfile
1
2
3
4
5
6
7
8
  ...
 (中略)
  ...
# Use jquery as the JavaScript library
gem 'jquery-rails'
  ...
 (中略)
  ...

記載されていない場合は最下部に記載し、bundle installする。

application.jsの記述を確認

application.jsに//= require jquery //= require rails-ujsの記載がされているか確認する。

application.js
1
2
3
4
5
6
  ...
  中略
  ...
//= require jquery
//= require rails-ujs 
//= require_tree .

記載がない場合は、上記を参考に記載する。

 必要なクラス名とIDを与える

後のajaxの非同期通信を実装するために、事前にコメントのフォームにクラス名を与えてく。app/views/tweets/show.html.erbの投稿フォームを以下のように編集し、先に進む。

 show.html.erbの中身が、form_with、か、form_tagのどちらを使用しているのか注意して進める。

form_tagの場合の修正点

下記のコードのとおり、以下3点を追記。正しく追記できないと、非同期通信の実装ができない。

  • new_commentというid
  • textboxというclass
  • form__submitというclass
app/views/tweets/show.html.erb
1
2
3
4
<%= form_tag("/tweets/#{@tweet.id}/comments",  method: :post,  id: "new_comment" ) do %>
  <textarea cols="30" name="text" placeholder="コメントする" rows="2" class="textbox"></textarea>
  <input type="submit" value="SEND" class="form__submit">
<% end %>

form_withの場合の修正点

下記のコードのとおり、以下3点を追記。正しく追記できないと、非同期通信の実装ができない。

  • new_commentというid
  • textboxというclass
  • form__submitというclass
app/views/tweets/show.html.erb
1
2
3
4
<%= form_with(model: [@tweet, @comment], local: true, id: "new_comment") do |form| %> 
  <%= form.text_area :text, placeholder: "コメントする" , rows: "2", class: "textbox" %>
  <%= form.submit "SEND", class: "form__submit" %>
<% end %>

コメント機能を非同期通信化する

コメント機能の非同期通信では、respond_toやAjaxの他に新しくjbuilderというものを使う。

コメント機能実装のステップ

  1. jQueryを記述するためのファイルを作成する
  2. フォームが送信されたら、イベントが発火するようにする
  3. 非同期通信でコメントが保存されるようにする
  4. respond_toを使用してHTMLとJSONの場合で処理を分ける
  5. jbuilderを使用して、作成したメッセージをJSON形式で返す
  6. 返ってきたJSONdoneメソッドで受取り、HTMLを作成する
  7. エラー時の処理を行う

1. jQueryを記述するためのファイルを作成する

jQueryを記述しながらAjaxによる非同期通信の実現を目指す。Ruby on Rails内では、JavaScriptファイルをassets/javascripts以下に作成する。

 comment.jsというファイルをassets/javascripts以下に作成する

  •  app
    •  assets
      •  javascripts
        • comment.js

2. フォームが送信されたら、イベントが発火するようにする

submitボタンを押した時に、イベントが発火するようにする。

 jQueryを記述

先ほど作成したcomment.jsに、コメントフォームが送信された時フォームに入力された値を受け取れるように以下の記述をする。

comment.js
1
2
3
4
5
6
$(function(){
  $('#new_comment').on('submit', function(e){
    e.preventDefault();
    var formData = new FormData(this);
  })
})

ポイント① フォームが送信された時、というイベントを設定したい場合は、form要素を取得してonメソッドを使う

要素を検証するとわかるが、今回のアプリの画面ではform要素のid属性#new_commentとなっている。フォームの送信についてonメソッドでイベントをセッティングする際は、form要素自体に設定するようにする。

ポイント② 最初から設定されているフォームの動作はキャンセルする

フォームが送信される時、何も設定していない状態(デフォルトの状態)だとフォームを送信するための通信が行われるため、preventDefault()を使用してデフォルトのイベントを止める。

 

 FormData

フォームのデータの送信に使用することができる。その他にも、キーのついたデータを伝送するためにフォームとは独立して使用することもできる。今回はコメントフォームがあるので、そのフォームの情報を取得するのに使う。

https://tech-master.s3.amazonaws.com/uploads/curriculums//57b19827260e3323ca619fb51a66c867.png

上の図のように、new FormData(フォーム要素)とすることでFormDataを作成できる。
今回FormDataオブジェクトの引数はthisとなっているが、イベントで設定したfunction内でthisを利用した場合は、イベントが発生したノード要素を指す。今回の場合は、new_commentというIDがついたフォームの情報を取得している。

 フォームの値を確認する

comment.js
1
2
3
4
5
6
7
$(function(){
  $('#new_comment').on('submit', function(e){
    e.preventDefault();
    console.log(this)
    var formData = new FormData(this);
  })
})

編集ができたら、検証の画面でコンソールを開き、thisの中身がなにか確認してみる。
以下の画像の様に、コンソール上にて、フォームに入力した値が出力されていれば正解!
JavaScriptではこのようにしてconsole.logを用いてデバッグを行う。
他にもdebuggerというデバッグ方法もある。

https://postd.cc/javascript-debugging-tips-and-tricks/

https://gyazo.com/69302f874250f38183670305d2be0096

3. 非同期通信でコメントが保存されるようにする

フォームに入力されたデータを取得したら、必要なAjax関数のオプションを揃えて非同期通信を行う。

実装の流れは以下。

  1. フォームの送信が行われた時に、Ajaxによる非同期通信でcreateアクションを動かす
  2. createアクション内でコメントを保存し、respond_toを使用してHTMLとJSONの場合で処理を分ける

 

comment.js
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
$(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
    })
  })
})

5行目でフォームの送信先のurlを定義している。$(this)は、thisで取得できる要素をjQueryオブジェクト化している。

 attrメソッド

要素が持つ指定属性の値を返す。
要素が指定属性を持っていない場合、関数はundefinedを返す。

今回はイベントが発生した要素のaction属性の値を取得しており、今回のaction属性にはフォームの送信先のurlの値が入っている。
これでリクエストを送信する先のURLを定義することができた。

続いて5~12行目がAjaxで非同期通信に必要なオプションを設定する。

 processDataオプション

デフォルトではtrueになっており、dataに指定したオブジェクトをクエリ文字列(例: msg.txt?b1=%E3%81%8B&b2=%E3%81%8D )に変換する役割がある。
クエリ文字列とは、WebブラウザなどがWebサーバに送信するデータをURLの末尾に特定の形式で表記したものの事。

 contentTypeオプション

サーバにデータのファイル形式を伝えるヘッダ。こちらはデフォルトでは「text/xml」でコンテンツタイプをXMLとして返してくる。

ajaxのリクエストがFormDataのときはどちらの値も適切な状態で送ることが可能なため、falseにすることで設定が上書きされることを防ぐ。FormDataをつかってフォームの情報を取得した時には必ずfalseにするという認識で構わない。
他にもAjaxリクエストを送信するオプションは多くある。

http://js.studio-kingdom.com/jquery/ajax/ajax

4. コメントを保存し、respond_toを使用してHTMLとJSONの場合で処理を分ける

非同期通信によって、comment#createを動かすことに成功し、正しくAjaxからdataを送れていれば、そのまま保存することが可能。
保存ができていることを確認したら、respond_toを使用してHTMLとJSONの場合で処理を書いていく。

comments_controller.rb
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
  def create
    @comment = Comment.create(comment_params)
    respond_to do |format|
      format.html { redirect_to tweet_path(params[:tweet_id])  }
      format.json
    end
  end

  private
 def comment_params
    params.require(:comment).permit(:text).merge(user_id: current_user.id, tweet_id: params[:tweet_id])
 end

form_tagで実装した場合はcomment_paramsの記述が異なるが修正の必要はない。

ローカル変数commentは、スコープの関係でこの後のjbuilder側で使用できないので、インスタンス変数@commentに編集する。

respond_to doを使用し、リクエストされたformatによって処理を分けるようにします。フォーマットがjsonの時の説明をします。todoリストのアプリではformat.jsonに引数としてインスタンスを渡しましたが、今回はjbuilderを使ってJavaScriptに返すデータを作成します。

 jbuilder

rails newコマンドでアプリケーションを作成した際にgemfileにデフォルトで記述されているgemで、入力データをJSON形式で出力するテンプレートエンジン。

今回はJbuilderを利用するので、コントローラ内でrender json: ○○を行わない。

5. jbuilderを使用して、作成したメッセージをJSON形式で返す

respond_toで処理を分けたら、jbuilderを使用してJavaScriptファイルに返すデータを作成する。
jbuilderは、viewと同じように該当するアクションと同じ名前にする必要がある。
commentのcreateアクションに対応するjbuilderのファイルになるので、作成するのはviews/comments/create.json.jbuilderになる。

 jbuilderのファイルを作成して、JavaScriptで必要な情報を渡すようにする。

views/comments/create.json.jbuilder
1
2
3
  json.text  @comment.text
  json.user_id  @comment.user.id
  json.user_name  @comment.user.nickname

jbuilderファイルでは基本的にjson.KEY VALUEという形で書くことができる。
こうすることによってJavaScriptファイルに返ってきたデータをjbuilderで定義したキーとバリューの形で呼び出して使うことができる。
複雑なjsonの生成も見やすく書くことができるので積極的にjbuilderを使っていく。

6. 返ってきたJSONdoneメソッドで受取り、HTMLを作成する

非同期通信の結果として返ってくるデータは、done(function(data) { 処理 })の関数の引数で受け取る。
この引数を元にHTMLを組み立てる。

comment.js
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
$(function(){
  function buildHTML(comment){
    var html = `<p>
                  <strong>
                    <a href=/users/${comment.user_id}>${comment.user_name}</a>

                  </strong>
                  ${comment.text}
                </p>`
    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);
      $('.comments').append(html);
      $('.textbox').val('');
      $('.form__submit').prop('disabled', false);
    })
  })
});

24-29行目は、非同期通信に成功した時の記述である。function(data)となっているが、非同期通信に成功した時の即時関数の第一引数には、サーバから返されたデータが入っている。この場合のサーバから返ってくるデータというのは、jbuilderで作成したcreate.json.jbuilderのデータである。
28行目の$('.form__submit').prop('disabled', false);は、 htmlの仕様でsubmitボタンを一度押したらdisabled属性というボタンが押せなく属性が追加される。
そのため、disabled属性をfalseにする記述を追加している。

2-11行目ではHTMLを追加している。簡単な記述で実現できるのは、テンプレートリテラル記法を使用しているから。

 テンプレートリテラル記法

ダブルクオートやシングルクオートの代わりにバックティック文字で囲むことで、複数行文字列や文字列内挿入機能を使用できる。
https://tech-master.s3.amazonaws.com/uploads/curriculums//ddc46a9e822e640436904b7e322d79e6.png
テンプレートリテラル記法を使用することで、わかりやすくHTML要素を作成できる!

buildHTMLの引数として渡されたcommentはサーバから返されたデータであるjbuilderのデータであるため、ファイル内で定義したキーとバリューの形式で使用することができる。

7.エラー時の処理を行う

最後に通信に失敗した場合の処理を実装する。
アラートを表示できれば成功。

comment.js
1
2
3
4
5
6
7
8
9
    .done(function(data){
      var html = buildHTML(data);
      $('.comments').append(html);
      $('.textbox').val('');
      $('.form__submit').prop('disabled', false);
    })
    .fail(function(){
      alert('error');
    })

サーバーエラーの場合、このfailの関数が呼ばれる。
以上でコメント機能の非同期通信化の実装は終了!