LoginSignup
92
84

More than 5 years have passed since last update.

Ajax でも Rails の flash メッセージを表示したい!

Last updated at Posted at 2014-10-10

概要

通常 Rails の flash メッセージは、アクションごとに HTML をレンダリングする場合にしか使用できません。
Rails 側の各アクションで flash メッセージの設定を行う必要があるからです。
つまり、Ajax 通信が終了 (complete) した際に flash メッセージを表示する手段は、デフォルトでは用意されていません。
一応、サーバ側で flash メッセージを表示するための JavaScript を用意して、それをレスポンスとして返すなどの方法はあります。

それでも Ajax 通信 (JSON リクエスト) でも flash メッセージを表示したいよぉ!
というわけでその方法を調べてみました。

方法

Rails 側ではレスポンスヘッダ ('X-' で始まるカスタムヘッダ) に flash メッセージを格納してレスポンスを返します。
そして、JavaScript 側ではそれを取り出すことで実現できました。

/app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception

  after_action :flash_to_headers

  private

  def flash_to_headers
    return unless request.xhr?

    response.headers['X-Flash-Messages'] = flash_json

    # ページをリロードした際に flash メッセージが残ってしまうのを防ぐ。 
    flash.discard
  end

  def flash_json
    flash.inject({}) do |hash, (type, message)|
      # XSS 対策を施す。
      message = "#{ERB::Util.html_escape(message)}"
      # 日本語のメッセージをレスポンスヘッダに含めるために URL エンコードしておく。
      message = URI.escape(message)
      hash[type] = message
      hash
    end.to_json
  end
end
/app/controllers/magicas_controller.rb
class MagicasController < ApplicationController
  respond_to :json, only: [:update]

  def update
    @magica = Magica.find(params[:id])
    @magica.update!(update_params)

    flash[:notice]  = '更新しました!'
    flash[:warning] = '更新しました?'
    flash[:error]   = '更新できず…orz'

    respond_with @magica
  end
end
/app/views/layouts/application.html.slim
doctype html
html lang="ja"
  head
    meta name="viewport" content="width=device-width, initial-scale=1.0"
    title Magica System
    - description_content = content_for?(:description) ? yield(:description) : "Bootstrap App"
    meta name="description" content=description_content

  = stylesheet_link_tag    "application", media: 'all', 'data-turbolinks-track' => true
  = javascript_include_tag "application", 'data-turbolinks-track' => true
  = csrf_meta_tags
  = yield(:head)

  body class=body_class
    = render 'layouts/navigation'
    .js-main-content.container
      / ここに flash メッセージを表示する。
      .js-flash 
        = render 'layouts/messages'
      = yield
/app/assets/javascripts/flash.js.coffee
# Underscore.js (あるいは Lo-Dash) を導入していることを前提とする。
$(document).ajaxComplete (event, xhr, settings) ->
  flash = $.parseJSON(xhr.getResponseHeader('X-Flash-Messages'))
  return if _.isEmpty(flash)

  $flash = $('.js-flash')
  $flash.empty()

  index = 1
  _.each flash, (message, type) ->

    alertType =
      switch type
        when 'notice'
          'success'
        when 'warning'
          'warning'
        when 'error'
          'danger'
        else
          'info'

    alertId = "js-alert-#{index}"
    decodedMessage = decodeURIComponent(message)

    $flash.append """
      <div class='alert alert-#{alertType}' data-alert-id='#{alertId}'>
        <a class='close' data-target='[data-alert-id=#{alertId}]' data-dismiss='alert'>&times;</a>
        <div>#{decodedMessage}</div>
      </i>
    """

    index++

  return

実行例

magica_system.png

でた! ✌ ('ω' ✌ )三 ✌ ('ω') ✌ 三( ✌ 'ω') ✌

参考

92
84
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
92
84