Posted at

ajaxの書き方〜.on('click')の罠〜

非同期通信を利用したアプリを作成しましたが

データが2重送信される事案が発生して、何時間も時間を無駄にしたので、その備忘録として。


1 rails newからGemfileの設定

$ rails new ajax-sample


Gemfile

gem 'jquery-rails'

gem 'jbuilder', '~> 2.5'

この二つを用意して

$ bundle install

これで、準備完了です。


2 MVCファイルの準備

今回は、コメントを入力して、表示するアプリを作成してみます。

$ rails g model Comment text:string

$ rails db:migrate
$ rails g controller Comments index


routes.rb

Rails.application.routes.draw do

root to: 'comments#index'
resources :comments, only: [:create]
end


comment.rb

class Comment < ApplicationRecord

validates :text, presence: true
end


comments_controller.rb

class CommentsController < ApplicationController

def index
@comments = Comment.all
end

def create
binding.pry
@comment = Comment.create(comment_params)
respond_to do |format|
format.json
end
end

private

def comment_params
params.require(:comment).permit(:text)
end

end


Prefix   Verb   URI Pattern           Controller#Action 

root GET / comments#index
comments POST /comments(.:format) comments#create


index.html.erb

<h1>Enter comment</h1>

<%= form_with model: Comment.new,url: :comments, class: "form" do |f| %>
<%= f.label "comment" %>
<%= f.text_field :text, class: "text" %>
<%= f.submit class: "button" %>
<% end %>
<ul>
<% @comments.each do |comment| %>
<li><%= comment.id %> <%= comment.text %></li>
<% end %>
</ul>

このままでは、コメントを作成するたびに、リダイレクトが発生して、読み込み待ちがおきてしまうため、非同期通信を導入してみましょう。


3 非同期通信の設定

非同期通信の流れとしては

送信ボタンをクリック→通常createアクションにデータが送信されるところをストップさせて、jsonデータのやりとりのみにする→データベースからjsonを引っ張ってきて、HTMLに追加する

といった流れです。

まずはapp/assets/javascriptsのapplication.jsに以下の記述があるか確認して、なければ追加します


application.js

//= require jquery

//= require rails-ujs

次に、app/assets/javascriptsにcomment.jsを作成します。


comment.js

$(function() {

$('.form').on('submit', function(){
var formData = new FormData(this);
var href = $('.form').attr('action');
$.ajax({
type: 'POST',
url: href,
data: formData,
dataType: 'json',
processData: false,
contentType: false
})

これで、投稿したデータをjsonファイルとして、createアクションに送ることができます。

[5] pry(#<CommentsController>)> params

=> <ActionController::Parameters {"utf8"=>"✓", "authenticity_token"=>"fYbMbCkLGkIudntFybf8hgGaVwGm68ZDyaFD0R5O+5EeJ+9iSB65a2NYhJcBgbcISCpmRV036KMdsPQRJ1J1Nw==", "comment"=>{"text"=>"Adele"}, "controller"=>"comments", "action"=>"create"} permitted: false>

[6] pry(#<CommentsController>)> params[:comment]
=> <ActionController::Parameters {"text"=>"Adele"} permitted: false>

formDataを取得して、createコントローラーにjsonファイルとして、データを送信しています。

先ほど作成した、comments_controller.rbでは、jsonファイルを受け取った時には、jsファイルにデータを送るように設定しています。


comments_controller.rb

  def create

binding.pry
@comment = Comment.create(comment_params)
respond_to do |format|
format.json
end
end

次は、jbuilderをインストールしたので、インスタンス変数をjsファイルで使えるようにします。

app/viewsにcreate.json.jbuilderファイルを作成します。(jsonファイルを送ったアクション名を記述し、コントローラー名と同じviewフォルダに作成します。)


create.json.jbuilder

json.text @comment.text

json.id @comment.id

これで、jsファイルでは、@comment.textをcomment.textとして、記述することが可能になりました。

最後に、comment.jsのajax以降を以下のように記述します。


comment.js

$(function() {

$('.form').on('submit', function(){
var formData = new FormData(this);
var href = $('.form').attr('action');
$.ajax({
type: 'POST',
url: href,
data: formData,
dataType: 'json',
processData: false,
contentType: false
})
.done(function(comment){
var html = `<li>${comment.id} ${comment.text}</li>`
$('ul').append(html);
})
.fail(function(){
alert("Enter text !!!");
$('.text').val('');
})
.always(function(){
$('.form')[0].reset();
$('.button').removeAttr('data-disabele-with');
})
return false; //.on('submit')の時はreturn false;でデータの2重送信を防ぐこと!!
});
});

これで、簡単な非同期通信を作成することができました。

ちなみに、送信ボタンをクリックしてイベントを発生させる場合(.on('click')の場合)は


comment.js

$('.button').on('click', function(e){

e.preventDefault();

以上の記述でOKであり、return falseは必要ありません。

作者はここで数時間の時間を費やしてしまいました。。。。