非同期通信でメッセージを送信する

#非同期通信でメッセージ送信をする手順を追ってみる

非同期通信について、理解不足ながらに処理の流れを追ってみました
自身が実装した時の流れを参考記事含め、追っているとも言えます
もう一度実装したい時にこれを見ればなんとなくでも分かるようにできればと思って書いてます
***

ここでは何をしているのかというと、チャットのメッセージ送信を非同期通信で行いたいというものです

##実装の手順
　1. フォームが送信されたらイベントが発火するようにする
　2. イベントが発火した時にajaxを使用して、messagesコントローラーのcreateアクションが動くようにする
　3. messagesコントローラーのcreateアクションでメッセージを保存してHTMLとJSONの場合で処理を分ける
　4. jbuilderを使用して、作成したメッセージをJSON形式で返す
　5. 帰ってきたJSONをdoneメソッドで受取り、HTMLを作成する(受取りが成功した場合）
　6. 失敗した場合の処理を追加する

###コードを見ながら手順を追ってみる

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

```js:app/assets/javascripts/message.js
$('#new_message').on('submit', function(e){ //$()でid属性を指定 ボタンを押して送信するというイベントをセット
    e.preventDefault() //デフォルトのイベントの停止 ここではフォームの送信を停止させる
```
`まず、送信ボタンを押してメッセージを送信したいので、ボタンを押して送信するというイベントを作る`
`ただ、そのまま送信されると通常通りの送信になるので、一旦ストップしてもらう`
　

**on()メソッド**
　様々なイベント処理に関して、イベント発生時に特定の処理を行う場合に使う
　対象となる要素とイベントとを結び付けているメソッド
　　参考記事
　　[【jQuery入門】on()によるイベント処理の使い方まとめ！](https://www.sejuku.net/blog/38774)

**preventDefault()メソッド**
　イベントを停止させます
　　参考記事
　　[JavaScriptのpreventDefault()って難しくない？preventDefault()を使うための前提知識](https://qiita.com/tochiji/items/4e9e64cabc0a1cd7a1ae)
　

####2. イベントが発火した時にajaxを使用して、messagesコントローラーのcreateアクションが動くようにする

```js:app/assets/javascripts/message.js
    var formData = new FormData(this); //フォーム要素の情報を取得する (this)はイベントが発生した要素を指す(onメソッド内のsubmitのこと)
    var url = $(this).attr('action'); //from要素内の'action'属性にあるURLを取得する
    $.ajax({
      url: url, //リクエスト先のURL： varで変数に代入した'action'属性にあるURL
      type: 'POST', //HTTP通信の種類： createアクションなので'POST'と記述
      data: formData, //コントローラーに送信するデータ： varで変数代入したFormDataのデータが送られる
      dataType: 'json', //コントローラーから受取るデータの種類： jsonを使うので'json'と記述
      processData: false, //dataに指定したオブジェクトをクエリ文字列に変換するかどうかを設定： 他の形式でデータを送るために自動変換を行いたくない場合はfalse
      contentType: false //サーバにデータを送信する際に用いるcontent-typeヘッダの値： ここではfalse
    })
```
`ここで送信をストップしてもらってる間に、ajaxを使って非同期通信するために必要な処理を施す
　・メッセージの送り先の設定
　・通信のタイプの設定
　・コントローラーにメッセージを渡すための設定
　・コントローラーからJSON形式になったメッセージを受け取るための設定
　　等々`

`そして、メッセージをコントローラーへ渡して、コントローラーからJSON形式になったメッセージを受取るまでをしてもらう`
　

**attrメソッド**
　引数に指定した属性の値を取得することができる
　ここでは'action'属性の値を取得している
　　参考記事
　　[30分で理解！jQueryのattr()で属性操作を極めるコツ！](https://www.sejuku.net/blog/37402)

**$.ajax()**
　Ajaxを送信するオプションをキーと値のペアで記述する
　　参考記事
　　[js STUDIO ajax()](http://js.studio-kingdom.com/jquery/ajax/ajax)

####3. messagesコントローラーのcreateアクションでメッセージを保存してHTMLとJSONの場合で処理を分ける

```ruby:app/controllers/messages_controller.rb
    def create
      #一部省略
        respond_to do |format| #フォーマットに合わせた形式のレスポンスを返す
          format.html { redirect_to group_messages_path(@group), notice: 'メッセージが送信されました' }
          format.json #json形式のレスポンスを返すために記述
        end
```
`ajaxからJSON形式にしてねと送られてきたメッセージをjbuilderでJSON形式にしてajaxにお返しする`
　

**respond_toメソッド**
　respond_to do |format|　～　end
　controller内で処理した結果を基本的にはHTML形式で返したいが、特定の呼び出しに対しては
　HTML以外の形式で返したい場合に使用するメソッド
　　参考記事
　　[Railsのrespond_toの使い方を現役エンジニアが解説【初心者向け】](https://techacademy.jp/magazine/24827)

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

```ruby:app/views/messages/create.json.jbuilder
    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
    json.id @message.id
```

`ajaxで送られてきたメッセージをjbuilderでJSON形式にして、コントローラーからajaxにお返ししてもらう`
　

jbuilderは該当アクションと同じ名前にする必要ある
なので、「create.json.jbuilder」という名前にする

メッセージを送信する時に必要な情報を記述
　・ユーザー名
　・送信時間
　・メッセージ内容
　・画像
　・ユーザーID

**jbuilderの書き方**
　参考記事
　[Railsのjbuilderの書き方と便利なイディオムやメソッド](https://qiita.com/ryouzi/items/06cb0d4aa7b6527b3645)
　

####5. 帰ってきたJSONをdoneメソッドで受取り、HTMLを作成する(受取りが成功した場合）

```js:app/assets/javascripts/message.js
    .done(function(data){ //帰ってきたデータが受け取れた時の処理
      var html = buildHTML(data); //(data)に帰ってきたデータ(jbuilderで作成したデータ)が入っている
      $('.messages').append(html); //受け取ったHTMLを'.messages'クラスの一番最後に追加する
      $('form')[0].reset(); //フォームの内容をリセットする
      $('input').prop('disabled', false) //一度押すと押せなくなる送信ボタンを連続で押せるようにしている
      $('.messages').animate({ scrollTop: $('.messages')[0].scrollHeight},50); //メッセージ送信後、最下部(送信したメッセージのところ)まで自動スクロールする
    })

```
非同期通信の結果として返ってくるデータは、done(function(引数) { 処理 })の関数の引数で受け取る



```js:app/assets/javascripts/message.js
    function buildHTML(message){
              var image = ( message.image ) ? `<img class="mesage__lower-info__image" src=${message.image}>` : '' ; //画像があれば画像を表示、なければ画像は表示しないという処理
              var html = `<div class="message", data-message-id="${message.id}">
                            <div class="message__upper-info">
                              <p class="message__upper-info__talker">
                              ${message.user_name}
                              </p>
                              <p class="message__upper-info__date">
                              ${message.created_at}
                              </p>
                            </div>
                            <div class="message__lower-info">
                              <p class="message__lower-info__text">
                              ${message.content}
                              </p>
                              ${image}
                            </div>
                          </div>`
              return html;
    }
```

`JSON形式で無事に受け取れたら、メッセージを送信して表示する`
`この時に、非同期通信でリロードすることなくメッセージが表示されるようになる`

　
doneメソッドで受け取ったdataをbuildHTMLメソッドに渡して、その返り値を上記のHTMLとして受け取っている

HTMLの記述はデベロッパーツールを参照して記述すれば良い
記述はテンプレートリテラルを使うと便利
　参考記事
　[使いこなそう！テンプレートリテラルとimport/exportステートメント](https://liginc.co.jp/273154)
　

**◎ ちょっと長いのでここでの流れのおさらい**
　1. doneで受け取ったデータをbuildHTMLメソッドに渡して、返り値をHTMLとして受取る
　2. append(html)でデータをメッセージの一番最後に表示する
　3. reset()でフォームの中身をリセットする
　4. 一度押すと押せなくなってしまう送信ボタンを押せるようにする
　5. 表示されたメッセージまでスクロールさせる

**append()メソッド**
　参考記事
　[jQueryのappendメソッドで要素を追加する方法](https://proengineer.internous.co.jp/content/columnfeature/12720)

**reset()メソッド**
　参考記事
　[resetメソッドを使ってフォームをリセットしよう](https://pikawaka.com/javascript/form-reset)

**4.について**
　送信ボタンを押すと、もう一度押すためにはリロードしないと使えなくなります
　これは仕様でそうなっているのだそうです
　デベロッパーツールで見ると、form内のsubmitに**disabled**という文字が表示されているはずです
　こいつが悪さをしています
　これを表示させないようにする必要があります
　そこで、disabledの設定を行うために、prop()メソッドを使用します
　　参考記事
　　[jQueryでdisabledの設定･解除･判定をする方法](https://www.flatflag.nir87.com/disabled-1783)

**5.について**
　メッセージのスクロールはanimate()メソッド、scrollTop()メソッドを使用して表示させています
　　参考記事
　　[【jQuery入門】scrollTop()で画面のスクロール位置を取得・設定する方法！](https://www.sejuku.net/blog/61689)
####6. 失敗した場合の処理を追加する

```js:app/assets/javascripts/message.js
    .fail(function(){ //帰ってきたデータが受け取れなかった時の処理
      alert ('メッセージ送信に失敗しました');
    })
```
メッセージの受取りに失敗した時、メッセージが空の状態で送信した時に「メッセージ送信に失敗しました」というアラートを出す
　
***
以上が非同期通信でメッセージを送信する一連の流れになります
ふわっとした部分もありますが、こんな流れかなと思います
勘違いしてる部分とかあれば指摘してもらえるとありがたいです

***

最後に今回のmessage.jsのコード全体を貼っておきます

```js:message.js
  function buildHTML(message){
            var image = ( message.image ) ? `<img class="mesage__lower-info__image" src=${message.image}>` : '' ;  //画像があれば画像を表示、なければ画像は表示されない
            var html = `<div class="message", data-message-id="${message.id}">
                          <div class="message__upper-info">
                            <p class="message__upper-info__talker">
                            ${message.user_name}
                            </p>
                            <p class="message__upper-info__date">
                            ${message.created_at}
                            </p>
                          </div>
                          <div class="message__lower-info">
                            <p class="message__lower-info__text">
                            ${message.content}
                            </p>
                            ${image}
                          </div>
                        </div>`
            return html;
  }

  $('#new_message').on('submit', function(e){ //$()でid属性を指定 ボタンを押して送信するというイベントをセット
    e.preventDefault() //デフォルトのイベントの停止 ここではフォームの送信を停止させる
    var formData = new FormData(this); //フォーム要素の情報を取得する (this)はイベントが発生した要素を指す(onメソッド内のsubmitのこと)
    var url = $(this).attr('action'); //from要素内の'action'属性にあるURLを取得する
    $.ajax({
      url: url, //リクエスト先のURL： varで変数に代入した'action'属性にあるURL
      type: 'POST', //HTTP通信の種類： createアクションなので'POST'と記述
      data: formData, //送信するデータ： varで変数代入したFormDataのデータが送られる
      dataType: 'json', //受け取るデータの種類： jsonを使うので'json'と記述
      processData: false, //dataに指定したオブジェクトをクエリ文字列に変換するかどうかを設定： 他の形式でデータを送るために自動変換を行いたくない場合はfalse
      contentType: false //サーバにデータを送信する際に用いるcontent-typeヘッダの値： ここではfalse
    })
    .done(function(data){ //帰ってきたデータが受け取れた時の処理
      var html = buildHTML(data); //(data)に帰ってきたデータ(jbuilderで作成したデータ)が入っている
      $('.messages').append(html); //受け取ったHTMLを'.messages'クラスの一番最後に追加する
      $('form')[0].reset(); //フォームの内容をリセットする
      $('input').prop('disabled', false) //一度押すと押せなくなる送信ボタンを連続で押せるようにしている
      $('.messages').animate({ scrollTop: $('.messages')[0].scrollHeight},50); //メッセージ送信後、最下部(送信したメッセージのところ)まで自動スクロールする
    })
    .fail(function(){ //帰ってきたデータが受け取れなかった時の処理
      alert ('メッセージ送信に失敗しました');
    })
  })

```