Wie gehen Sie mit Rail's Flash mit Ajax-Anfragen um?

77

Ich bin ziemlich zufrieden mit der Lösung , die ich gefunden habe. Grundsätzlich habe ich eine Hilfsmethode, die den Flash-Inline neu lädt, und dann habe ich einen after_filter, der den Flash löscht, wenn die Anforderung xhr ist. Hat jemand eine einfachere Lösung als diese?

Update: Die obige Lösung wurde in Rails 1.x zurückgeschrieben und wird nicht mehr unterstützt.

Jerry Cheung
quelle
3
Ich mag Ihre Lösung ..after_filter { flash.discard if request.xhr? }
Peter Ehrlich

Antworten:

64

Sie können die Flash-Nachrichten auch mithilfe eines after_filter-Blocks in den Antwortheadern speichern und mit Javascript anzeigen:

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

Fügen Sie in application.js einen globalen Ajax-Handler hinzu. Für jquery machen Sie so etwas:

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

Ersetzen Sie alert () durch Ihre eigene Javascript-Flash-Funktion oder versuchen Sie es mit jGrowl.

gudleik
quelle
6
Darüber hinaus können Sie den Nachrichtentyp speichern: response.headers['X-Message-Type'] = flash_type(während flash_type den wichtigsten Typ zurückgibt (Fehler> Erfolg> Hinweis). Außerdem können Sie ajaxCompletedann Erfolgsfälle einschließen:$(document).ajaxComplete(function(e, request, opts) { fireFlash(request.getResponseHeader('X-Message'), request.getResponseHeader('X-Message-Type')); });
knusprig
Wenn Ihre Rails-Anwendung Prototype verwendet, um eine Ajax-Anforderung zu stellen, funktioniert der vorherige JQuery-Handler nicht. Sie müssen die entsprechenden Prototype-Handler verwenden. Siehe meine Antwort oben.
Emzero
1
Warum sagen die Kommentare oben in application.js, dass es nicht ratsam ist, dort Code hinzuzufügen?
Steve
29

Und hier ist meine auf @emzero basierende Version mit Änderungen für jQuery, die auf Rails 3.2 getestet wurden

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}"
Victor S.
quelle
Dies funktioniert auch auf Schienen 3.1.0. Vielen Dank, Victor, hat sofort für mich gearbeitet.
LearningRoR
2
Tolle Zusammenstellung, sollte aber nicht "$ (" # flash-message "). AjaxComplete (Funktion (Ereignis, Anfrage)" "$ (Dokument)" sein?
nullnullnull
Aus irgendeinem Grund "es sei denn, flash [type] .blank?" funktioniert in manchen Situationen nicht richtig. Bei einer Aktion ohne Blinken wird der Satz "Fehler, Warnung, Hinweis" angezeigt. Ich habe dies in js mit der Zeile 'if (msg! = "Fehler, Warnung, Hinweis") show_ajax_message (msg, type)' gepatcht, aber dies ist offensichtlich eine hackige Lösung. Ich kann jedoch die wahre Ursache des Problems nicht herausfinden.
nullnullnull
1
[:error, :warning, :notice].each {}Gibt das Array zurück, wenn die Rückgabebedingung nicht erfüllt ist. Daher muss dieser Code etwas angepasst werden. Andernfalls ist er hilfreich.
Radixhound
3
Wenn der Code überarbeitet wurde, um die Zeichenfolge "Fehler, Warnung, Hinweis" nicht zurückzugeben und mit Twitter-Bootstrap gist.github.com/hbrandl/5253211
Hartwig
15

Dies wird in der js-Antwort benötigt

Wenn Sie RSJ verwenden:

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

Wenn Sie jQuery verwenden:

$("#flash_notice").html(<%=escape_javascript(flash.delete(:notice)) %>');
Silviu Postavaru
quelle
3
Es sieht so aus, als müssten Sie in Rails 3.1+ flash.discard(:notice)nichtflash.delete(:notice)
Adrian Macneil
15

Ich habe es so gemacht ..

Controller :

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

Aussicht:

<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
quelle
In Ihrem Controller kann ich einen Tippfehler sehen. flash.now[:notice]= @msg / 'blah blah..'Ich war auch neugierig. Sie würden post.js.erb in den Pfad app / assets / javascripts einfügen, nicht wahr?
RajG
1
post.js.erb wird in den Ansichtsordner des Controllers verschoben, in diesem Fall "views / posts / post.js.erb". msg / "bla bla" ich meinte msg oder eine zufällige Nachricht wie "
bla bla
10

Auf anderen aufbauen -

(Wir übergeben das vollständige Flash-Objekt als JSON, sodass wir das gesamte Flash-Objekt im Browser rekonstruieren können. Dies kann verwendet werden, um sicherzustellen, dass alle Flash-Nachrichten angezeigt werden, falls mehrere Flash-Nachrichten von Rails generiert werden.)

#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
quelle
Danke. Ich habe die Funktion in application.js so geändert, dass sie gefällt, $.each( $.parseJSON(request.getResponseHeader('X-Flash-Messages')), function(key,value){ flash_message(key,value) });wenn Sie die js-Funktion haben, um eine Flash-Nachricht mit dem Parametertyp Flash und Nachricht
anzuzeigen
6

Sieht so aus, als ob Sie etwas benötigen flash.now[:notice], das nur in der aktuellen Aktion und nicht in der nächsten verfügbar ist. Sie können sich die Dokumentation hier ansehen: http://api.rubyonrails.com/classes/ActionController/Flash/FlashHash.html#M000327

Nakajima
quelle
Hey, das gefällt mir! Sonst niemand ? : S
Bachet
1
@le_Daf: Die Verwendung flash.nowist eine Lösung für ein anderes Problem. Der Inhalt von flash.nowwird nicht auf magische Weise durch einen Ajax-Rückruf in die aktuelle Seite eingefügt.
Verwirrung
3
Hätte flash.eventually statt flash.now heißen sollen;)
Deborah
5

Weisen Sie die Nachricht im Controller folgendermaßen zu:

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

app / views / layouts / application.js.erb - Layout für Ajax-Anfragen. Hier können Sie einfach verwenden

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

oder mit einigen umfangreichen Animationen mit Gritter: http://boedesign.com/demos/gritter/

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

Basierend auf gudleik Antwort:

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

Fügen Sie dann in Ihrer application.js (wenn Sie native Prototyp-Helfer von Rails verwenden) Folgendes hinzu:

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
   }
});
Emzero
quelle
können Sie erklären return unless request.xhr? Ich meine am Ende der aktuellen Anfrage, wenn wir Flash-Hinweise hinzugefügt haben, fügen wir sie zu den Antwortheadern hinzu, dann lesen wir sie in js - cool, aber ich bin nicht genau sicher, warum wir die obige Zeile haben
Michael K. Madison
3

Es gibt ein Juwel namens Unobtrusive Flash , das Flash-Nachrichten automatisch in ein Cookie codiert. Ein Javascript auf Client-Seite sucht nach Flash und zeigt es an, wie Sie möchten. Dies funktioniert nahtlos sowohl bei normalen als auch bei Ajax-Anfragen.

lulalala
quelle
Alle Probleme für mich gelöst.
WM
3

Ich habe die Antwort von Victor S geändert, um einige Fälle zu beheben, in denen flash[type].blank?nicht funktioniert hat, wie von wenigen Personen in den Kommentaren angegeben.

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

Dann ist die Ruhe gleich

// 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
quelle
3

Hier ist meine Version (Arbeiten mit mehreren Flash-Hinweisen und UTF-8-Codierung für Sonderzeichen):

Innerhalb von 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

In meinem Kaffeeskript (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>")
Luc Boissaye
quelle
Es sollte mit asiatischen Zeichen mit der utf8-Dekodierung funktionieren ;-)
Luc Boissaye
1
Nein, ist es nicht!. Ich habe das RFC-Dokument überprüft und festgestellt, dass im http-Header nur ASCII-Zeichen unterstützt werden.
Fengd
1

Eine andere Möglichkeit wäre, das Div "Notice" mit der Nachricht vom "OnFailure" -Handler Ihrer Ajax-Anfragen zu aktualisieren / anzuzeigen. Sie haben die Möglichkeit, diese Flash-Meldungen mit der erforderlichen Wirkung anzuzeigen. Ich habe das benutzt

 render: text => "Es ist ein Fehler aufgetreten" ,: status => 444

im Javascript

 neue AjaxRequest (...

  ,
   OnFailure: Funktion (Transport) {
      $ ("# Notice"). update (transport.responseText);
     // zeige die Nachricht  
   }}

);

HTH

Rishav Rastogi
quelle
1

Ich baue eine Engine, die ein gewisses Verhalten an den application_controller enthält, um die Flash-Nachricht im Antwortheader zu senden, wie einige von euch vorschlagen.

https://github.com/bonzofenix/flajax

Bonzofenix
quelle
0

Die einzige Verbesserung, die ich mir vorstellen kann, besteht darin, die Seite.reload_flash als Standard festzulegen (sie muss nicht in jede rjs-Datei eingefügt werden und expizitiert werden, wenn Sie den Flash nicht neu laden möchten, z. B. page.keep_flash.

Ich würde nicht wissen, wo ich anfangen soll, aber ich bin mir sicher, dass es nicht so schwer ist, einige Schienen zu kennen.

krusty.ar
quelle
0

Wenn Sie AJAX-Aufrufe verwenden möchten, sollte redirect_to nicht in der Steuerung verwendet werden. Flash-Nachrichten sollten vielmehr explizit bezeichnet werden:

In your_controller:

respond_to :js

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

In der Ansicht, die von your_ajax_method_in_the_controller benannt wird

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);
    })

Bitte beachten Sie, dass die Nachrichtenklasse ein Ankerpunkt zum Rendern von Nachrichten ist. Diese Klasse sollte in Ihrer Ansicht oder Ihrem Anwendungslayout vorhanden sein. Wenn Sie ERB verwenden, wird die Linie$('.messages').html("<%= j(render 'layouts/messages') %>");

Das oben in HAML / ERB eingebettete JavaScript ist der Schlüssel zum Anzeigen von Flash-Nachrichten bei Verwendung von AJAX. Alle anderen Komponenten bleiben für Nicht-AJAX-Aufrufe gleich.

Sie können your_ajax_method_in_the_controller.js.coffee.js verwenden oder einfach, aber dann sind die Rails-Variablen für JS / Coffee nicht verfügbar. Obwohl ich hier keine Variablen verwende, ziehe ich es vor, JS in HAML zu verpacken, um eine konsistente Codebasis zu erhalten.

Ich nutze Twitter Bootstrap zum Stylen von Nachrichten, um $(".alert").alert('close')den Hinweis auszublenden. Und hier sind die Nachrichten teilweise:

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}"

Nur für den Fall, CSS für die Warnungen ist unten

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