How do you handle Rail's flash with Ajax requests?

Ruby on-RailsAjax

Ruby on-Rails Problem Overview


I'm pretty happy with the solution that I came up with. Basically, I have a helper method that reloads the flash inline, and then I have an after_filter that clear out the flash if the request is xhr. Does anyone have a simpler solution than that?

Update: The solution above was written back in Rails 1.x and is no longer supported.

Ruby on-Rails Solutions


Solution 1 - Ruby on-Rails

You can also store the flash messages in the response headers using a after_filter block and display them using javascript:

class ApplicationController < ActionController::Base
after_filter :flash_to_headers

def flash_to_headers
  return unless request.xhr?
  response.headers['X-Message'] = flash[:error]  unless flash[:error].blank?
  # repeat for other flash types...

  flash.discard  # don't want the flash to appear when you reload page
end

And in application.js add a global ajax handler. For jquery do something like this:

$(document).ajaxError(function(event, request) {
  var msg = request.getResponseHeader('X-Message');
  if (msg) alert(msg);
});

Replace alert() with your own javascript flash function or try jGrowl.

Solution 2 - Ruby on-Rails

And here is my version based on @emzero, with modifications to work with jQuery, tested on Rails 3.2

application_controller.rb
class ApplicationController < ActionController::Base
	protect_from_forgery

	after_filter :flash_to_headers

	def flash_to_headers
		return unless request.xhr?
		response.headers['X-Message'] = flash_message
		response.headers["X-Message-Type"] = flash_type.to_s

		flash.discard # don't want the flash to appear when you reload page
	end

	private

	def flash_message
		[:error, :warning, :notice].each do |type|
			return flash[type] unless flash[type].blank?
		end
	end

	def flash_type
		[:error, :warning, :notice].each do |type|
			return type unless flash[type].blank?
		end
	end
end
application.js
// FLASH NOTICE ANIMATION
var fade_flash = function() {
	$("#flash_notice").delay(5000).fadeOut("slow");
	$("#flash_alert").delay(5000).fadeOut("slow");
	$("#flash_error").delay(5000).fadeOut("slow");
};
fade_flash();

var show_ajax_message = function(msg, type) {
	$("#flash-message").html('<div id="flash_'+type+'">'+msg+'</div>');
	fade_flash();
};

$(document).ajaxComplete(function(event, request) {
	var msg = request.getResponseHeader('X-Message');
	var type = request.getResponseHeader('X-Message-Type');
	show_ajax_message(msg, type); //use whatever popup, notification or whatever plugin you want
});
layout: application.html.haml
		#flash-message
			- flash.each do |name, msg|
				= content_tag :div, msg, :id => "flash_#{name}"

Solution 3 - Ruby on-Rails

This is needed in the js response

If you are using RSJ:

page.replace_html :notice, flash[:notice]
flash.discard

If you are using jQuery:

$("#flash_notice").html(<%=escape_javascript(flash.delete(:notice)) %>');

Solution 4 - Ruby on-Rails

I did it this way..

controller: > respond_to do |format| flash.now[:notice] = @msg / 'blah blah...' format.html format.js end

view:

<div id='notice'>
	<%= render :partial => 'layouts/flash' , :locals => { :flash => flash } %>
</div>        

layouts/_flash.html.erb

<% flash.each do |name, msg| %>
			<div class="alert-message info"> 
				<a class="close dismiss" href="#">x</a> 
				<p><%= msg %></p>
			</div>
<% end %>

post.js.erb

$("#notice").html("<%= escape_javascript(render :partial => 'layouts/flash' , :locals => { :flash => flash }).html_safe %>");

Solution 5 - Ruby on-Rails

Building on top of others -

(We pass the complete flash object as JSON, enabling us to reconstruct the complete flash object in the browser. This can be used to ensure that all flash messages are displayed in case multiple flash messages are generated by Rails.)

#application_controller.rb
class ApplicationController < ActionController::Base
  after_filter :flash_to_headers
  
  def flash_to_headers
    if request.xhr?
      #avoiding XSS injections via flash
      flash_json = Hash[flash.map{|k,v| [k,ERB::Util.h(v)] }].to_json
      response.headers['X-Flash-Messages'] = flash_json
      flash.discard
    end
  end
end

//application.js
$(document).ajaxComplete(function(event, request){
  var flash = $.parseJSON(request.getResponseHeader('X-Flash-Messages'));
  if(!flash) return;
  if(flash.notice) { /* code to display the 'notice' flash */ $('.flash.notice').html(flash.notice); }
  if(flash.error) { /* code to display the 'error' flash */ alert(flash.error); }
  //so forth
}

Solution 6 - Ruby on-Rails

Looks like what you need is flash.now[:notice], which is only available in the current action and not in the next. You can take a look at the documentation here: http://api.rubyonrails.com/classes/ActionController/Flash/FlashHash.html#M000327

Solution 7 - Ruby on-Rails

Assign the message in the controller like this:

  flash.now[:notice] = 'Your message'

app/views/layouts/application.js.erb - Layout for Ajax Requests. Here you can simply use

  <%= yield %>
  alert('<%= escape_javascript(flash.now[:notice]) %>'); 

or with some rich animations using gritter: http://boedesign.com/demos/gritter/

  <%= yield %>
  <% if flash.now[:notice] %>
    $.gritter.add({
      title: '--',
      text: '<%= escape_javascript(flash.now[:notice]) %>'
    });
  <% end %>

Solution 8 - Ruby on-Rails

Based on gudleik answer:

class ApplicationController < ActionController::Base
  after_filter :flash_to_headers

def flash_to_headers
  return unless request.xhr?
  response.headers['X-Message'] = flash_message
  response.headers["X-Message-Type"] = flash_type

  flash.discard # don't want the flash to appear when you reload page
end

private

def flash_message
  [:error, :warning, :notice].each do |type|
    return flash[type] unless flash[type].blank?
  end
end

def flash_type
  [:error, :warning, :notice].each do |type|
    return type unless flash[type].blank?
  end
end

Then on your application.js (if you're using Rails native Prototype helpers) add:

Ajax.Responders.register({
onComplete: function(event, request) {
   var msg = request.getResponseHeader('X-Message');
   var type = request.getResponseHeader('X-Message-Type');
   showAjaxMessage(msg, type); //use whatever popup, notification or whatever plugin you want
   }
});

Solution 9 - Ruby on-Rails

There is a gem called Unobtrusive Flash that automatically encodes flash messages into a cookie. A javascript at client end checks for flash and display it in whatever way you want. This works seamlessly in both normal and ajax requests.

Solution 10 - Ruby on-Rails

I modified Victor S' answer to fix some cases where flash[type].blank? didn't work as noted by few people in the comments.

after_filter :flash_to_headers

def flash_to_headers
   return unless request.xhr?
   response.headers['X-Message'] = flash_message
   response.headers["X-Message-Type"] = flash_type.to_s

   flash.discard # don't want the flash to appear when you reload page
end

private

def flash_message
   [:error, :warning, :notice, nil].each do |type|
     return "" if type.nil?
     return flash[type] unless flash[type].blank?
   end
end

def flash_type
   [:error, :warning, :notice, nil].each do |type|
       return "" if type.nil?
       return type unless flash[type].blank?
   end
end

Then rest is the same

// FLASH NOTICE ANIMATION

var fade_flash = function() {
    $(".flash_notice").delay(5000).fadeOut("slow");
    $(".flash_alert").delay(5000).fadeOut("slow");
    $(".flash_error").delay(5000).fadeOut("slow");
};

var show_ajax_message = function(msg, type) {
    $(".flash_message").html('<div class="flash_'+type+'">'+msg+'</div>');
    fade_flash();
};

$( document ).ajaxComplete(function(event, request) {
    var msg = request.getResponseHeader('X-Message');
    var type = request.getResponseHeader('X-Message-Type');
    show_ajax_message(msg, type); //use whatever popup, notification or whatever plugin you want
 
});

Solution 11 - Ruby on-Rails

Here is my version (working with multiples flash notices and special characters UTF-8 encoding):

Inside ApplicationController:

after_filter :flash_to_headers
def flash_to_headers
  return unless request.xhr?
  [:error, :warning, :notice].each do |type|
    if flash[type]
      response.headers["X-Ajax-#{type.to_s.humanize}"] = flash[type]
    end
  end
  flash.discard
end

Inside my coffee-script (twitter bootstrap version):

css_class = {
    Notice: 'success',
    Warning: 'warning',
    Error: 'error'
}
$(document).ajaxComplete (event, request) ->
  for type in ["Notice", "Warning", "Error"]
    msg = request.getResponseHeader("X-Ajax-#{type}")
    if msg?
      $('#notices').append("<div class=\"alert #{css_class[type]}\">#{decodeURIComponent(escape(msg))}</div>")

Solution 12 - Ruby on-Rails

Another way would be update/display the "notice" div with the message from the your Ajax requests "OnFailure" handler. It gives you the ability to show these flash messages with required effect. I used this

render :text => "Some error happened", :status => 444

in the Javascript

new AjaxRequest(...

, OnFailure:function(transport) { $("#notice").update(transport.responseText); // show the message
}

);

HTH

Solution 13 - Ruby on-Rails

I build an engine that includes some behavior to the application_controller to send the flash message in the response header as some of you guys propose.

https://github.com/bonzofenix/flajax

Solution 14 - Ruby on-Rails

The only improvement I can think of is making the page.reload_flash default (not having to put it on every rjs file, and make it expicit if you don't want to reload the flash, something like page.keep_flash.

I wouldn't know where to start but knowing some rails I'm sure it's not that hard.

Solution 15 - Ruby on-Rails

In case you want to use AJAX calls redirect_to should not be used in the controller. Rather, flash message should be explicitly denoted:

In your_controller:

respond_to :js

def your_ajax_method
  flash[:notice] = 'Your message!'
end

In the view that is named by your_ajax_method_in_the_controller

your_ajax_method_in_the_controller.js.haml

:plain
  $("form[data-remote]")
    .on("ajax:success", function(e, data, status, xhr) {
      $('.messages').html("#{escape_javascript(render 'layouts/messages')}");
      setTimeout(function(){ $(".alert").alert('close') }, 5000);
    })

Please, notice, that the messages class is an anchor point for rendering messages. This class should be present in your view or application layout. If you use ERB the line becomes $('.messages').html("<%= j(render 'layouts/messages') %>");

The above JavaScript embedded into HAML/ERB is the key to displaying flash messages when using AJAX. All other components remain the same for non-AJAX calls.

You may use your_ajax_method_in_the_controller.js.coffee or plain .js but then the rails variables won't be available to JS/Coffee. Even though I don't use variables here I prefer to wrap JS in HAML to keep consistent codebase.

I leverage Twitter Bootstrap for styling messages, thus $(".alert").alert('close') fades away the notice. And here is the messages partial:

layouts/_messages.html.haml

- flash.each do |name, msg|
  - if msg.is_a?(String)
    .alert-messages
      %div{class: "alert alert-#{name == :notice ? "success" : "error"} fade in"}
        %a.close{"data-dismiss" => "alert"} 
          %i.icon-remove-circle
        = content_tag :div, msg, id: "flash_#{name}"

Just in case, CSS for the alerts is below

.alert-messages {
  position: fixed;
  top: 37px;
  left: 30%;
  right: 30%;
  z-index: 7000;
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJerry CheungView Question on Stackoverflow
Solution 1 - Ruby on-RailsgudleikView Answer on Stackoverflow
Solution 2 - Ruby on-RailsVictor SView Answer on Stackoverflow
Solution 3 - Ruby on-RailsSilviu PostavaruView Answer on Stackoverflow
Solution 4 - Ruby on-RailsdbKooperView Answer on Stackoverflow
Solution 5 - Ruby on-RailsVikrant ChaudharyView Answer on Stackoverflow
Solution 6 - Ruby on-RailsnakajimaView Answer on Stackoverflow
Solution 7 - Ruby on-RailsArun Kumar ArjunanView Answer on Stackoverflow
Solution 8 - Ruby on-RailsempzView Answer on Stackoverflow
Solution 9 - Ruby on-RailslulalalaView Answer on Stackoverflow
Solution 10 - Ruby on-RailsRicky GuView Answer on Stackoverflow
Solution 11 - Ruby on-RailsLuc BoissayeView Answer on Stackoverflow
Solution 12 - Ruby on-RailsRishav RastogiView Answer on Stackoverflow
Solution 13 - Ruby on-RailsbonzofenixView Answer on Stackoverflow
Solution 14 - Ruby on-Railskrusty.arView Answer on Stackoverflow
Solution 15 - Ruby on-RailsVadym TyemirovView Answer on Stackoverflow