やりたいこと
ちょっとした入力をモーダル上でやりたい。
入力内容に不備があって、登録出来ない場合、画面遷移せずにそのままモーダル上でエラーを表示したい。
入力成功したら、メッセージと共にリダイレクトして画面を再描画したい。
前提
- simple_form
- twitter bootstrap
- haml
- device
処理の流れ
- View : formからajax呼び出す
- Controller : ajax呼び出しを受け取るコントローラー、レスポンスの返し方
- View : サーバーからのレスポンスをajaxで受け取って、画面を更新
サンプル実装する処理の概要
仮想のオークションサイトに入札する処理を想定。
以下の要件を満たしたい:
- オークションには最低入札価格が指定されている
- 入札者はオークション詳細からモーダル上で入札金額を入力し、入札する
- 入札金額がオークションの最低入札価格を下回っている場合、入札は受け付けられない
Model
class Auction < ActiveRecord::Base
belongs_to :user
has_many :bids
validate_presence_of :user_id, :name, :min_price
end
class Bid < ActiveRecord::Base
belongs_to :user
belongs_to :auction
validate_presence_of :user_id, :price, :auction_id
before_save :acceptable_price?
def acceptable_price?
if self.auction.min_price < self.price
true
else
self.errors.add(:price, '入札金額が低すぎます。')
false
end
end
end
POINT
入札を表すBidモデルに、保存前のコールバックを指定している。
親オークションの最低入札価格より入札価格が下回っている場合は、保存させないようにしている。
View : formからajax呼び出す
= render 'form'
= simple_form_for [ @auction, @bid ], remote: true do
= f.input :price, label: '入札金額'
= f.button :submit, class: 'btn btn-primary'
POINT
- formのレンダリング時に
remote: true
を加える。
Controller : ajax呼び出しを受け取るコントローラー、レスポンスの返し方
remote: true
でリクエストが飛んでくるので、受け取るコントローラー側では format.js
が呼び出される。
format.html
はデフォルトなので一応用意してみた。
htmlの時は成功・失敗ともメッセージと共にリダイレクトするのみ。
class BidsController < ApplicationController
before_action :authenticate_user!
# ...
def create
@bid = current_user.bids.new(bid_params)
@bid.auction = @auction
respond_to do |format|
if @bid.save
flash[:notice] = '入札しました。'
format.html { redirect_to @auction }
format.js { render js: "window.location = '#{auction_path(@auction)}'" }
else
@bid.errors.each do |name, msg|
flash.now[name] = msg
end
format.html { redirect_to @auction }
format.js { render partial: "shared/message", status: :unprocessable_entity }
end
end
end
# ...
end
POINT
成功時の処理
-
render js:
で続く文字列を JavaScript としてブラウザに返す - リダイレクトしたいパスを
window.location
にセットすると、レスポンスを受け取った時点でブラウザがJavaScriptコードを実行してくれる
参考URL : レイアウトとレンダリング | Rails ガイド | Vanilla JavaScriptを出力する
render js: "alert('Hello Rails');"
上のコードは、引数で与えられた文字列をMIMEタイプtext/javascriptでブラウザに送信します。
参考URL : Ajaxから指定のURLへリダイレクトする【Rails】 | Kntmrkm.new()
失敗時の処理
- 失敗した時は、リダイレクトしたくない
- 画面上に表示させたいエラーメッセージは、フラッシュメッセージに詰める
- リダイレクトしないので
flash.now
を使ってメッセージを渡す -
render partial
で部分テンプレートをhtmlとしてレスポンスボディに入れて返す
参考URL : Action Controller の概要 | Rails ガイド | flash.now
View : サーバーからのレスポンスをajaxで受け取って、画面を更新
$ ->
$('#auction_modal').on("ajax:success", (e, data, status, xhr) ->
# フラッシュメッセージ付きでリダイレクトするだけなので、success時はココでは何もしない
).on "ajax:error", (e, xhr, status, error) ->
message = xhr.responseText
$('#error-message').html(message)
POINT
成功時の処理
コメントの通り、レスポンスをJavaScriptコードとして受け取ったら、ブラウザが自動でリダイレクトしてくれるだけなので、自前のコード上では何もしない。
失敗時の処理
上述のrender partial
で部分テンプレートをhtmlとしてレスポンスボディが xhr.responseText
に入っているので、それをそのままhtmlとして画面上に埋め込む。
おまけ
エラーメッセージを生成するパーシャル
- flash.each do |name, msg|
- if msg.is_a?(String)
%div{ class: "alert alert-#{name == "notice" ? "success" : "danger" }"}
%button.close{ area: { hidden: "true" }, data: { dismiss: "alert" }, type: "button" } ×
= content_tag :div, msg, id: "flash_#{name}"