WeBlog

Webに関する情報を中心に発信してるブログ

Ajax通信について

Ajaxとは?

Ajax(エイジャックス)とは、Asynchronous JavaScript + XMLの略で

JavaScriptから非同期でサーバーへ通信を行い、データを取得できる技術です。(非同期通信)

Ajaxを用いて、ページの1部分を動的に書き換えることができます。

Ajaxを使っているサービスとして有名なのが「Googleマップ」です。

f:id:weblog_tec:20210424232149p:plain f:id:weblog_tec:20210424232201p:plain

例えばこのサイトでブックマークをするボタンがあります。

ここでボタンを押すと、ブックマークのボタンを「黒塗り・白塗り」に切り替えることができます。

Ajax通信をしない場合は、毎回毎回ブックマークのボタンを押すと、画面がリロードされます。

リロードされるということは、ブラウザからサーバーにリクエストを投げて、全てのHTMLを取得して表示しているということです。

画面の1部しか変わらないの、全体のHTMLを取得してくるのは非常に効率が悪いです。

Ajaxを使うとページの一部分だけを書き換えることができるようになります。

通常の通信

f:id:weblog_tec:20210424232235j:plain

①例えば、http://localhost:3000/boardsファイルをくださいという形でパスを指定してサーバーに対してリクエストします。

②サーバー側では、リクエストされたファイルがあれば、Rubyのプログラムはサーバー側で動きます。

なのでサーバー側でRubyのコードを処理します。

リクエスト → ルーティング → コントローラーのアクション → モデルと連携 → ビューのレンダリング

という流れです。

③処理が終わったファイル(HTMLファイル)をレスポンスでブラウザに返すことで画面が切り替わります(更新される)。

これが今までやったような通常の通信です。

この通常の通信のことを「同期通信」と言います。

一瞬画面が白くなった後、画面が切り替わるような通信は、全てこの同期通信です。

ただし、この仕組みだと今回のようにページの1部分しか変わらないのに、通常の通信では全部のHTMLデータをサーバーから取ってきて画面に表示する形になります。

なので、リクエストを投げて、レスポンスが返るまでユーザーは何もできないです。

クライアントからサーバーに対してページ全ての情報を返すようリクエストが送られているため、リクエストを送ったクライアントは、サーバーからの応答があるまでその結果を待機し、結果を受け取った後に画面全体を切り替える処理を行います。

毎回毎回1部分しか変更しないだけなのに、全部のHTMLをサーバーに通信して、サーバーからファイルを取ってきて、表示するという効率が悪く、時間がかかる通信になります。

非同期通信

f:id:weblog_tec:20210424232302j:plain

①ブラウザの方ではJSを使って、例えばボタンが押されたときにAjax通信を行う処理を前もってプログラムを書いておきます。(〇〇.jsというファイルを用意しておく)

②ボタンが押されたらAjax通信でサーバー側にリクエストを送ります。

その際にこのファイルをくださいという形でURLを指定します。

その時に指定するのがAjax用のファイルです。

③リクエストしたAjax用のファイルがRubyコードで書かれていれば、Rubyコード部分をサーバー側で処理します。

④処理したファイルをレスポンスという形でクライアントにファイルを返します。

例えばHTMLの形式でデータが返ってきた場合は、ブラウザ側でHTMLのデータを取得して、JSを使ってHTMLファイルを1部分に埋め込んで書き換えます。

こうすることで1部分だけデータを書き換えることができます。

このようにJSでページ全部のHTMLを取得しないで、1部のHTMLを取得して、それを元にJSでページの1部分を書き換えることができるので、効率がいいです。

これがAjax通信の基本的な流れとメリットです。

非同期通信では、クライアントからのリクエスト送信後、サーバーの処理中にも、他の作業を行うことができます。

一般的なAjaxのコード

<!DOCTYPE html>
<html lang="ja">

<head>
  <meta charset="UTF-8">
  <title>タイトル</title>
  <link rel="stylesheet" href="style.css">
</head>

<body>
  <form method="post" action="" class="formArea js-formArea">
    <div class="js-set-html">
      <p>ここだけ変わる</p>
      
      名前
      <input type="text" name="name" class="js-get-val-name">

      <input type="submit" value="送信">
  </form>
  <script src="jquery-3.3.1.min.js"></script>
  <script src="ajax.js"></script>
</body>

</html>

一般的にはjQueryですが、$.ajaxというメソッドを使ってAjax通信を実現します。

このコード自体は特に意味はありませんが、Ajax用のJSファイルは下記のように書きます。

$(function(){
#  フォームのjQueryオブジェクトを取得して、submitというイベントを設定
$('.js-formArea').on('submit', function (e) {
    # 同期通信を止める
        e.preventDefault();
        
    # Ajax通信 
    $.ajax({
      type: 'post', // 送信の方法
      url: 'sample.html', // 送信先のパス
      dataType: 'html', // やり取りするデータ形式
      data: { // サーバー側に渡すデータ
        name: $('.js-get-val-name').val()
      }
        # Ajax成功
    }).then(function(data, status) {
      $('.js-set-html').html(data);
        # Ajax失敗
    }).fail(function (data) {
            alert('Ajax失敗');
    });
  });

});

Railsにおけるajax通信

RailsAjax通信を実現するには、2つの方法があります。

1、〇〇.jsというファイルを用意して、そこにイベントとajax処理を書いて、<script src="app.js"></script>コードでJSファイルを読み込んで、イベントが発火したらAjax通信するようにする

2、remote: trueを指定する方法

今回はRailsremote: trueを使ったAjax通信について書いていきます。

Railsでremote: trueを使ったAjax通信

Railsには組み込みのAjaxがあり、それを使うことで簡単にAjax通信を実現できます。

ディレクトリ構成

今回のディレクトリ構成は下記です。

app
 |- /controlers
        |- /likes_controller.rb
 |- /views
              |- /boards
              |- /_like.html.erb
              |- /_unlike.html.erb
        |- /likes
              |- /create.js.erb
              |- /destroy.js.erb
        

まずは、link_toメソッドの引数にremote: trueオプションを追加します。

remote: trueオプションを加えることで、Railsが自動でAjaxでサーバーにリクエストを送信できるようにしてくれます。(Rails-ujsの機能)

なので、先ほどのコードのように〇〇.jsというファイルを用意して、$.ajax()みたいなメソッドは書く必要がありません。

HTMLのコードを見てみると、data-remote="true"属性が付与されます。

RailsAjaxでは、このdata-remote="true"属性を見てAjaxの処理を実行します。

form_withではデフォルトでAjax通信をするようになっているので、local: trueを付与して、Ajax通信を無効にしています。

# _unlike.html.erb
<%= link_to パス指定, id: "js-like-#{board.id}", method: :delete, remote: true do %>
  削除リンク
<% end %>

# HTMLの出力
<a id="js-like-25" data-remote="true" rel="nofollow" data-method="delete" href="パス">
    削除リンク
</a>

# _like.html.erb
<%= link_to パス指定, id: "js-like-#{board.id}", method: :post, remote: true do %>
  追加リンク
<% end %>

# HTML
<a id="js-like-26" data-remote="true" rel="nofollow" data-method="post" href="パス">
  追加リンク
</a>

今までの通信は、

1、リンクを押す

2、リンクのhref属性に指定してあるパスにリクエストを送信

3、サーバー側でコントローラーのアクションに割り振る。そこでDBに保存したりと処理をして、Viewファイルを用意してブラウザに返す。

4、ブラウザはレスポンスで返ってきたHTMLを読み込んで、画面を表示する。

リクエスト → ルーティング → コントローラー/アクション → モデルと連携 → ビュー みたいな流れです。

なので、users_controllerindexアクションが呼び出された場合、対応するviewテンプレートは、views/users/index.html.erb でした。

ちなみに〇〇.html.erbはHTMLコードの中にRubyコードを埋め込むことができるファイルです。

remote: trueを指定したAjax通信の場合は

リンクをクリックすると、ルーティングを経由し、users_controllerindexアクションに、remote: trueのオプション付きで通信を投げることになります。

remote: trueを指定することによってhtmlではなくjsファイルで実行されます。

つまり、処理の流れは、users_controllerのindexアクションを通過した後、index.html.erbではなく、index.js.erbファイルに向かうこととなります。

拡張子が「js.erb」となっているファイル内にはjsの処理+Rubyのコードを記述をすることができます。

JSコードの中にRubyのコードを埋め込むことができるファイルということです。

コントローラーを記述する

コントローラー側で大切なことは、redirectしないことです。

リダイレクトすると、「302」のステータスコードが返って、指定されたURLにブラウザからリクエストされて、結局が画面が更新される「同期処理」になってしまうからです。

リダイレクトやレンダーを指定しなければcreate.js.erbdestroy.js.erbを使ってレスポンスが生成されます。

class LikesController < ApplicationController
  def create
    # テーブルにデータを保存するコード
  end

  def destroy
    # テーブルからデータを削除するコード
  end
end

JSファイルを用意する

remote: trueのオプションをつけた場合、likes_controller.rb#createが実行された場合は、views/likes/create.je.erbが要求されます。

$("#js-like-<%= @board.id %>").replaceWith("<%= j(render('boards/unlikes', board: @board)) %>");

likes_controller.rb#destroyが実行された場合は、views/likes/destroy.je.erbが要求されます。

$("#js-like-<%= @board.id %>").replaceWith("<%= j(render('boards/like', board: @board)) %>");

このコードが何をやっているかですが、

$("#js-like-<%= @board.id %>")

上記の部分は、id属性js-like-22みたいなid属性が付与されている、HTMLを取得して、$()という形でjQueryで用意されているいろんなメソッドが使えるようにオブジェクトの形に変えています。

console.log($("#js-like-<%= @board.id %>"))

上記のように書くと、ディベロッパーツールでオブジェクトをみることができます。

<%= @board.id %>Rubyのコードです。

〇〇.js.erbというファイルでは、JSのなかにRubyのコードを埋め込むことができます。

これによって、id属性の動的な部分をRubyのコードで指定しています。

replaceWith("<%= j(render('boards/like', board: @board)) %>");

replaceWithというメソッドは、要素を置き換えるメソッドです。

$(A).replaceWith(B);

AをBに置き換えます。

j()はescape_javascriptエイリアスです。

エスケープという無害化をしてくれています。

要は悪意のあるコードが埋め込まれて、実行されることを防ぐためのものです。

エイリアスというのはショートカットみたいなもので、下記のように書いても同じです。

replaceWith("<%= escape_javascript(render('boards/like', board: @board)) %>");

https://apidock.com/rails/ActionView/Helpers/JavaScriptHelper/escape_javascript

https://api.rubyonrails.org/classes/ActionView/Helpers/JavaScriptHelper.html

このようなjavascriptが生成され、それがクライアントにレスポンスとして返されます。

クライアントはこのjavascriptを実行します。

今回のケースだとこのjavascriptを実行した結果、ブックマーク用のボタンの表示が置き換わり、画面の1部分変化します。

ちなみの〇〇.js.erbファイルはJavaScriptコードにRubyのコードを埋め込むことができるので、〇〇.js.erbRubyコードを使って条件分岐することもできます。

下記のような感じです。

# create.js.erb
$("#error_messages").remove()
<% if @comment.errors.present? %>
    # エラーがあれば下記のコードを返す
  $("#new_comment").prepend("<%= j(render('shared/error_messages', object: @comment)) %>")
<% else %>
    # エラーがないなら下記のコードを返す
  $("#js-table-comment").prepend("<%= j(render('comments/comment', comment: @comment)) %>")
  $("#js-new-comment-body").val('')
<% end %>

if文で@commentにエラーメッセージがあるかどうかで、ブラウザに返すJSコードを変えてます。

Ajax通信を見てみる

Ajax通信はディベロッパーツールのNetWorkタブでXHRという部分をしてするとAjax通信だけを見ることができます。

下記のような感じでサーバーからブラウザに返ってきます。

このコードはJSのコードなので、ブラウザ側で読み込まれて、実行されます。

なので、JSコードが上から1行1行実行されて、対象のjQueryオブジェクトをreplaceWithメソッドで要素が置き換わることで画面の1部分が変わります。

$("#js-like-26").replaceWith("<a id=\"js-like-26\" data-remote=\"true\" rel=\"nofollow\" data-method=\"post\" href=\"パス">\nリンク\n<\/a>

ちなみにj()がない場合は下記です。

\\nがないことがわかります。

エスケープされていないコードです。

$("#js-like-26").replaceWith("<a id="js-like-26" data-remote="true" rel="nofollow" data-method="post" href="パス">
リンク
</a>");

なのでAjax通信では

リクエスト → ルーティング → コントローラー#アクション → コントローラー名/アクション名.js.erb → 〇〇.js.erbでHTMLファイルをrenderする(しない時もある) → JSファイルをブラウザにレスポンス → ブラウザで受け取ったJSファイルを実行

という流れです。

〇〇.js.erbというファイルを挟んでいるところが味噌です。

https://railsguides.jp/working_with_javascript_in_rails.html

replaceWithメソッドとhtmlメソッドの違い

# _like.html.erb
<a id="js-like-26" data-remote="true" rel="nofollow" data-method="post" href="パス">
    追加リンク
</a>

# HTML
<a id="js-like-26" data-remote="true" rel="nofollow" data-method="post" href="パス">
  追加リンク
</a>

# _unlike.html.erb
<a id="js-like-25" data-remote="true" rel="nofollow" data-method="delete" href="パス">
    削除リンク
</a>

<a id="js-like-25" data-remote="true" rel="nofollow" data-method="delete" href="パス">
  削除リンク
</a>

htmlメソッド

htmlメソッドの場合、もともと上記のどちらかが存在していた場合に1回リンクを押すと下記のようになります。

<a id="js-like-26" data-remote="true" rel="nofollow" data-method="delete" href="パス">
    <a id="js-like-26" data-remote="true" rel="nofollow" data-method="post" href="パス">
        追加リンク
    </a>
</a>

もう一回押してみると下記のようにあります。

<a id="js-like-26" data-remote="true" rel="nofollow" data-method="delete" href="パス">
    <a id="js-like-26" data-remote="true" rel="nofollow" data-method="delete" href="パス">
        削除リンク
    </a>
</a>

htmlメソッドは指定したHTMLタグの中身を変える(中身を追加する)ことがわかります。

replaceWithメソッド

replaceWithメソッドの場合は、指定したHTMLタグそのものと、その中身をかえることがわかる。

なのでaタグが重複することはないです。

<a id="js-like-26" data-remote="true" rel="nofollow" data-method="post" href="パス">
    追加リンク
</a>
<a id="js-like-26" data-remote="true" rel="nofollow" data-method="delete" href="パス">
    削除リンク
</a>