77

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.

Stephen Horvath
  • 5,188
  • 3
  • 24
  • 31
Jerry Cheung
  • 1,258
  • 1
  • 12
  • 13

15 Answers15

64

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.

Benjamin Crouzier
  • 40,265
  • 44
  • 171
  • 236
gudleik
  • 3,011
  • 1
  • 18
  • 4
  • 6
    In addition, you can store the message type: `response.headers['X-Message-Type'] = flash_type` (whereas flash_type returns the most important type (error > success > notice). Also, you can use `ajaxComplete` then to include success cases: `$(document).ajaxComplete(function(e, request, opts) { fireFlash(request.getResponseHeader('X-Message'), request.getResponseHeader('X-Message-Type')); });` – crispy Dec 20 '10 at 17:00
  • If your rails application is using Prototype to make ajax request, the previous jquery handler won't work. You'll need to use the corresponding Prototype handlers. See my answer above. – empz Feb 15 '11 at 18:05
  • 1
    why do the comments at the top of application.js say it's not advisable to add code there? – Steve Feb 24 '12 at 09:33
29

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}"
Keith
  • 1,404
  • 1
  • 19
  • 24
Victor S
  • 5,098
  • 5
  • 44
  • 62
  • This also works on rails `3.1.0`. Thanks Victor, worked right out of the box for me. – LearningRoR Aug 08 '12 at 21:33
  • 2
    Great compilation, though shouldn't "$("#flash-message").ajaxComplete(function(event, request)" be "$(document)"? – nullnullnull Jan 13 '13 at 02:32
  • For some reason, 'unless flash[type].blank?' doesn't work properly in some situations. An action with no flashes will render the sentence 'error, warning, notice'. I patched this in the js by using the line, 'if (msg != "error, warning, notice") show_ajax_message(msg, type)', but this is obviously a hacky solution. I can't figure out the real cause of the problem, though. – nullnullnull Jan 31 '13 at 17:15
  • 1
    `[:error, :warning, :notice].each {}` returns the array if the return condition is not met, so this code needs a bit of adjusting.. but otherwise, helpful. – radixhound Mar 25 '13 at 21:49
  • 3
    If reworked the code to not return the string "error, warning, notice" as well as play nice with twitter-bootstrap https://gist.github.com/hbrandl/5253211 – Hartwig Mar 27 '13 at 10:43
  • this only works if you have nothing else going on via ajax. For example, if you're using datatables then every ajaxComplete will trigger this. Is there a way to have flash notifications trigger it's own event? – Michael K Madison Jun 23 '19 at 12:49
15

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)) %>');
Silviu Postavaru
  • 1,784
  • 4
  • 15
  • 19
15

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 %>");
dbKooper
  • 1,035
  • 10
  • 17
  • In your controller, I can see a typo in `flash.now[:notice]= @msg / 'blah blah..'` I was also curious, you would put post.js.erb in path app/assets/javascripts wouldn't you? – RajG Dec 18 '13 at 10:15
  • 1
    post.js.erb goes in controllers's view folder in this case its "views/posts/post.js.erb" . msg / "blah blah" i meant msg or some random message like "blah blah" – dbKooper Dec 18 '13 at 13:20
10

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
}
Vikrant Chaudhary
  • 11,089
  • 10
  • 53
  • 68
  • Thnx. I modified function in application.js to like this `$.each( $.parseJSON(request.getResponseHeader('X-Flash-Messages')), function(key,value){ flash_message(key,value) });` if you have js function to show flash message with params type of flash and message – Rahul garg Feb 05 '13 at 12:46
6

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

nakajima
  • 1,862
  • 12
  • 12
5

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 %>
Arun Kumar Arjunan
  • 6,827
  • 32
  • 35
3

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

});
Ricky Gu
  • 585
  • 1
  • 4
  • 13
3

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>")
AlexKtf
  • 13
  • 5
Luc Boissaye
  • 935
  • 8
  • 14
3

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
   }
});
empz
  • 11,509
  • 16
  • 65
  • 106
  • can you explain `return unless request.xhr`? I mean at the end of the current request if we;ve added any flash notices we add them to the response headers then in js we read them -- cool, but I'm not exactly sure why we have the above line – Michael K Madison Jun 28 '19 at 09:48
3

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.

lulalala
  • 17,572
  • 15
  • 110
  • 169
1

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

bonzofenix
  • 635
  • 5
  • 12
1

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

Rishav Rastogi
  • 15,484
  • 3
  • 42
  • 47
0

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;
}
Vadym Tyemirov
  • 8,288
  • 4
  • 42
  • 38
0

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.

krusty.ar
  • 4,015
  • 1
  • 25
  • 28