0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

非同期通信(Ajax)を実装してみる

0
Last updated at Posted at 2020-07-17

はじめに

Ajaxって何? → 非同期通信の総称とか言われてます
何ができんの? → 例えばページをリロードせずにデータのやりとりが可能

今記事では、簡単なアプリケーションで実際に非同期通信を行えるよう実装していきます。


非同期通信の流れ

非同期通信ではJavaScriptのメソッドを利用してリクエストを送るため、
フォーム要素のデフォルトアクションを無効にする必要がある。
今回のアプリで言うと【Create Todo】ボタンを押すことによって
リクエストが送られることがデフォルトアクション。

現状は下記の通り、新しいTodoが作成されると
左上のリロードマークが動いてページが更新される。
このデフォルトアクションの無効化は後述。
Image from Gyazo

非同期通信のリクエストがJavaScriptのメソッドを利用することはわかりました。
じゃぁレスポンスは?
デフォルトアクションを無効化すると同時に、レスポンスをJSON形式で返して欲しいということをコントローラーのアクションに明記しなければいけないらしい。JSON形式のファイルはviewsフォルダに作成可能。
→ファイルの形式は○○.json.jbuilder
※JSONとはRubyのハッシュと同様、キーとバリューの組合わせでデータを表現する形式

非同期通信ここまでのまとめ

①リクエストはJavaScriptのメソッドを利用
②レスポンスはJSONで
③デフォルトアクションがあれば無効化


早速、非同期通信に取り掛かりたいところですが、その前に準備があります。4つほど。

①respond_to メソッドでリクエストがHTML,JSONどっちのレスポンスを求めているか条件分岐

todos_controller.rb
  # def create
  #   Todo.create(todo_params)
  #   redirect_to :root
  # end

  # ↓↓↓↓↓ respond_to メソッドで条件分岐させる ↓↓↓↓↓

  def create
    @todo = Todo.create(todo_params)
    respond_to do |format|
      format.html { redirect_to :root }
      format.json { render json: @todo}
    end
  end

②Ajaxで実装していくためにjQueryのGemを導入

Gemfileに gem 'jquery-rails' を追記してbundle install

③ディレクトリを整える

app
 └ assets
     ├ config
     ├ images
     ├ javascripts            ←①作成
     │     ├ application.js   ←②作成
     │     └ todo.js          ←③作成
     └ stylesheets

④呼び出すために色々記述

app/views/layouts/application.html.haml
= javascript_include_tag 'application'を追記し、
viewファイルから呼び出せるようにします。

続いて、app/assets/config/manifest.jsに追記します。

manifest.js
//= link_tree ../images
//= link_directory ../javascripts .js  ←この行を追記
//= link_directory ../stylesheets .css

最後にapp/assets/javascripts/application.jsに記述

application.js
//= require jquery    ←RailsでjQueryを使用するためのもの
//= require rails-ujs ←RailsでJavaScriptを使用するためのもの
//= require_tree .    ←application.jsが存在するディレクトリのJavaScriptファイル全てを読み込む

はい ここまでで準備完了。
まだ非同期通信実装の入り口に立っただけ...さすが初学者の山場。

実装に取り掛かかります

todo.js
$(function() {
  $('.js-form').on('submit', function(e) {            
    e.preventDefault();                           
    let todo = $('.js-form__text-field').val();
  });
});

①まず大前提として**$(function(){...})**が必要。jQueryを使う時は絶対記述するくらいの感覚

②2行目の**$('.js-form').on('submit', function(e)**
まずは前半 $('.js-form') は下記の2行目のクラス名の部分

index.html.haml
.contents
  = form_with model: @todo, html: { class: 'form js-form' }, local: true do |f|
    = f.text_field :content, class: 'form__text-field js-form__text-field'
    = f.submit class: 'form__submit js-submit'
  %ul.todos
    = render @todo

今回はクラス名ですが、$(#'〇〇')とidを使用することも可能。
後半、submitイベントを設定 (.js-form
が送信されたときに実行されるイベント)
function(e) の e 今回のコードで言うとsubmitイベント発火時の情報を取得してるみたい。
イメージ的には $('.js-form').on('submit', までの情報が入ってる感じかな

③そして3行目の e.preventDefault( );
初めに言ってたデフォルトアクションを無効化させる魔法のコード

④4行目は変数todoに、f.text_fieldで入力された値を代入
**val()**がvalue値を取得しています


さらにtodo.jsに記述していきます

todo.js
$(function() {
  $('.js-form').on('submit', function(e) {            
    e.preventDefault();                           
    let todo = $('.js-form__text-field').val();
// ↓↓↓↓↓ ここから続き ↓↓↓↓↓
    $.ajax({
      type: 'POST',
      url: '/todos.json',
      data: {
        todo: {
          content: todo
        }
      },
      dataType: 'json'
    })
  });
});

⑤**$.ajax**は非同期通信を行うための記述

⑥**$.ajax以下のtype url data dataTypeajaxメソッドのオプション**

オプション 説明
type HTTP通信の種類で、GETとPOSTがある
url リクエスト送信先のurl
data サーバに送信する値
dataType サーバから返されるデータの型 (json一択では?)

ややこしいのがdataの部分で、横書きすると↓
{ todo: { content: todo(テキストフィールドに入力された値)} }

まとめると
通信方法POSTで、**'/todos.json'**というURLに
{ todo: { content: todo(テキストフィールドに入力された値)} }を送信しますよ。
サーバからはjson
で値を返してね。ってこと。


さらにさらにtodo.jsに記述していきます

todo.js
$(function() {
  $('.js-form').on('submit', function(e) {            
    e.preventDefault();                           
    let todo = $('.js-form__text-field').val();
    $.ajax({
      type: 'POST',
      url: '/todos.json',
      data: {
        todo: {
          content: todo
        }
      },
      dataType: 'json'
    })
// ↓↓↓↓↓ ここから続き ↓↓↓↓↓
    .done(function(data) {
    })
    .fail(function() {
      alert('error');
    });
  });
});

非同期通信を**done(成功)fail(失敗)**で処理を分けてます

done(function(data)のdata(引数名は別になんでも良い)は
リクエストによって返ってきたレスポンスが代入される
レスポンスは確かJSONでって言ってたよな...てことでtodos_controller.rbを見ると

todos_controller.rb
def create
  @todo = Todo.create(todo_params)
  respond_to do |format|
    format.html { redirect_to :root }
    format.json { render json: @todo}    ココ
  end
end

上記のようにjsonの場合は作成したTodoをjson形式にして返す記述がしてある
この@todoをどうするかですが、今記事の初めのほうに言っていた
『JSON形式のファイルはviewsフォルダに作成可能。ファイル形式は○○.json.jbuilder』を実行

⑨viewsフォルダの中のtodoフォルダにjbuilderファイルを作成

app
 └ views
     ├ todos                 
     │   ├ _todo.html.haml   
     │   ├ index.html.haml   
     │   └ create.json.jbuilder   ←作成 ※createはアクション名

create.json.jbuilderに記述

create.json.jbuilder
json.content @todo.content

@todo.contentの内容がjson.contentで送られますよという意味

じゃ**@todo.content**ってどこから出てきたのって話
ターミナルでコンソールを起動して、@todo = Todo.firstを実行すると

=> #<Todo id: 1, content: "test1", ...省略>

データベースのtodosテーブルにある、1つ目の投稿情報全てが出てきました。

続けて、@todo.content を実行すると

rb(main):002:0> @todo.content
=> "test1"

todosテーブルにある、1つ目の投稿のcontentカラム情報が表示される。

話を元に戻すと、今回はこの**"test1"って内容がjson.content**で送られてますよってこと
@todo.contentはどこから出てきた → コンソールで確認するとわかりやすい

⑪jbuilderでブラウザに表示させるHTMLを作成
以下のtest1 test2 test3はそれぞれ
【Create Todo】ボタンが押されるたびに生成されている
Image from Gyazo

検証ツールで確認すると``

 〇〇 `` というまとまりになっている。このまとまりをbuildTodoで作成
todo.js
$(function() {
      // ↓↓↓↓↓ 追記① ↓↓↓↓↓
  function buildTodo(form){
    let html = `<li class="todo">
                  ${form.content}
                </li>
                `
    return html
  }
      // ↑↑↑↑↑ 追記  ↑↑↑↑↑
  $('.js-form').on('submit', function(e) {
    e.preventDefault();
    let todo = $('.js-form__text-field').val();
    $.ajax({
      type: 'POST',
      url: '/todos.json',
      data: {
        todo: {
          content: todo
        }
      },
      dataType: 'json'
    })

    .done(function(data) {
      // ↓↓↓↓↓ 追記① ↓↓↓↓↓
      let html = buildTodo(data)
      // ↑↑↑↑↑ 追記  ↑↑↑↑↑

      // ↓↓↓↓↓ 追記③ ↓↓↓↓↓
      $('.todos').append(html)
      // ↑↑↑↑↑ 追記  ↑↑↑↑↑

    })
    .fail(function() {
      alert('error');
    });
  });
});

追記①では、描画内容をbuildTodoで生成している。
htmlという変数に、先ほど検証ツールで確認したまとまり(DOM)を代入
〇〇の部分にはbuildTodo(form)の引数.content ※引数はなんでも良い

追記②は、非同期通信が成功したときHTML情報を描画するという記述
buildTodoを呼び出している

追記③では、ajaxで非同期取得したHTMLをappendで反映させてます
$('.todos')のクラス名はどこからきているかと言うと↓
Image from Gyazo
それぞれのli要素を囲っているul要素のクラス**".todos"**です。
このクラス名を指定することでul要素内に新たにli要素が生成されていきます。


さて、現状はここまできています。↓
Image from Gyazo
非同期通信はできていて、リロードなしでも投稿内容が更新されている。
ここで2つ気になることが出てきました。

1.前回の投稿内容が残ったまま
2.【Create Todo】ボタンが一度しか押せない

これら2点を解消していきます。

まず1つ目
前回の投稿内容を投稿と同時に消えるようにする

todo.js
$(function() {
  function buildTodo(form){
    let html = `<li class="todo">
                  ${form.content}
                </li>
                `
    return html
  }
  $('.js-form').on('submit', function(e) {
    e.preventDefault();
    let todo = $('.js-form__text-field').val();
    $.ajax({
      type: 'POST',
      url: '/todos.json',
      data: {
        todo: {
          content: todo
        }
      },
      dataType: 'json'
    })

    .done(function(data) {
      let html = buildTodo(data)
      $('.todos').append(html)
      // ↓↓↓↓↓ 追記 ↓↓↓↓↓
      $('#todo_content').val("");
      // ↑↑↑↑↑ 追記 ↑↑↑↑↑
    })
    .fail(function() {
      alert('error');
    });
  });
});

$('#todo_content').val("")を追記
val("")とすることで値を空にできます。先ほどのval()と似ているので注意
#todo_contentはどこから?
Image from Gyazo
検証ツールで確認 → 上記の青枠のところを見てみるとid="todo_content
とあります。
なのでここのidを引っ張ってきたわけです。別にクラスでも良いと思いますが。

続いて2つ目
【Create Todo】ボタンをリロードせずに押せるようにする

todo.js
$(function() {
  function buildTodo(form){
    let html = `<li class="todo">
                  ${form.content}
                </li>
                `
    return html
  }
  $('.js-form').on('submit', function(e) {
    e.preventDefault();
    let todo = $('.js-form__text-field').val();
    $.ajax({
      type: 'POST',
      url: '/todos.json',
      data: {
        todo: {
          content: todo
        }
      },
      dataType: 'json'
    })

    .done(function(data) {
      let html = buildTodo(data)
      $('.todos').append(html)
      $('#todo_content').val("");
      // ↓↓↓↓↓ 追記 ↓↓↓↓↓
      $(".form__submit").prop('disabled', false);
      // ↑↑↑↑↑ 追記 ↑↑↑↑↑
    })
    .fail(function() {
      alert('error');
    });
  });
});

**$(".form__submit").prop('disabled', false);**を追記
.form__submitは【Create Todo】ボタンのクラス名を使っています。

prop()は、引数にプロパティを指定することでプロパティの値を取得できます。
disabledは無効化させる属性ですので、それをfalse
で無効化しないようにします。

ここでいう無効化とは、ボタンが押せなくなるということ。それがfalseなので有効化です。


これでようやく完成です↓
Image from Gyazo

①リロードされずにブラウザが更新されている
②前回の投稿内容が消えている
③ボタンを連続で押すことができる

全部達成できました。


まとめ

非同期通信は一度コードを書いてしまえば、
テンプレとして使えるところがたくさんある印象です。

めちゃめちゃ細かくコードを読み解いていったので記事が長くなってしまいました..
ご意見、アドバイス等々、なんでもお待ちしております。

ここまで読んでいただきありがとうございます。

0
1
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
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?