0

As an inexperienced but quick-learning Rails developer, I want to write a controller after-filter that checks for AJAX requests and returns some HTML in a custom header that can be used by the response handler to display any flash messages.

For consistency and conformance to DRY, I want to create that partial HTML using the same app/views/layouts/_messages.html.erb(.haml) that Rails generated.

After viewing several posts, my solution is thus:

  # adapted from http://stackoverflow.com/a/2729454/270511
  def flash_to_headers
    return unless request.xhr?
    response.headers['X-flash'] = render_to_string partial: 'layouts/messages.html'
    flash.discard  # don't want the flash to appear when you reload page
  end

This actually returns the desired headers, but also throws an exception.

Subsequent searching revealed several posts where the respondents claimed that calling render_to_string from an after-filter is not supported.

One alternate solution I am thinking of is to render layouts/navigation.html a second time from views/layouts/application.html.haml, but enclosed in a div where display=none. To accomplish this with the same template, I will have to add some dummy place-holder messages to flash[:notice, :warn, and :error] because otherwise _messages.html.haml was written not to render sections when there are no related messages. Then I can use the placeholders as templates to clone and fill in with flash messages returned by subsequent AJAX requests.

While this might work, I think it is ugly and has an unpleasant smell. (I am inexperienced at Rails, but not at software development!) It may be possible that I have missed something due to my lack of experience with Rails (and lack of available mentors). If so, I will be grateful if someone can point out a cleaner way of accomplishing my goal.

If not, I will re-post this to the issues list for rails on Github. I think that displaying flash messages to indicate some feedback from an AJAX request to the end-user is a common enough use-case that Rails should support it better.

Lawrence I. Siden
  • 9,191
  • 10
  • 43
  • 56
  • Is there a reason why you would not consider using the standard unobtrusive Javascript where you use format.js in the response in your action in Rails, and call a common JS function that flashes a message from the create.js.erb, update.js.erb, etc that is returned from each Ajax call? – mccannf Feb 02 '14 at 23:08
  • I think I'm already doing something like that now. The logic for creating the placeholders for the AJAX flash messages is in layouts/application.html.haml and it does it by rendering the generated _message.html.haml partial with placeholder messages. The Javascript that clones the placeholders and displays the received message are currently local to the Ajax response handler, but soon I'll isolate it in a separate file and then package the whole thing in it's own gem for review and improvement by the community. Thanks for the thought. – Lawrence I. Siden Feb 03 '14 at 03:35

1 Answers1

0

I consider my AJAX requests as API calls, and therefore I only send minimal information in my message headers, and only send the most relevant error message

application_controller.rb

return unless request.xhr?

if type = priority_flash # Method that returns most relevant message
  response.headers['X-Message'] = flash[type]
  response.headers['X-Message-Type'] = type.to_s

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

The job of rendering the error message should belong to the javascript files

some_js_file.js

// AJAX callbacks
$(document).ajaxComplete(function(e, request, opts) { 
    fireFlash(request.getResponseHeader('X-Message'), request.getResponseHeader('X-Message-Type')); 
});

$(document).ajaxError(function(event, jqxhr, settings, thrownError){
    fireFlash("AJAX request failed : " + jqxhr.status + " "+thrownError, "danger")
});

/* AJAX Flash messages */
function fireFlash(content, type){
    if (content === null){
        return
    }
    var alert_type;
    switch(type){
        case "alert":
        case "danger":
        case "fatal":
        case "error":
            alert_type= "danger"
            break;
        case "success":
        case "notice":
            alert_type= "success"
            break;
        case "warning":
            alert_type= "warning"
        default:
            alert_type= "info"
    }
    $("#flashes").prepend($("<div/>")
        .addClass("alert alert-dismissible alert-ajax alert-"+alert_type)
        .html((decodeURIComponent(escape(content))))
        .append('<button type="button" class="close" data-dismiss="alert"><span aria-hidden="true">&times;</span><span class="sr-only">Close</span></button>')
    )
    document.location.href = "#top";
}
Cyril Duchon-Doris
  • 12,964
  • 9
  • 77
  • 164