Как вы справляетесь с Flash Rail с Ajax-запросами?
Я очень доволен решением, которое я придумал. По сути, у меня есть вспомогательный метод, который перезагружает встроенную флэш-память, а затем у меня есть фильтр after_filter, который очищает флэш-память, если запрос имеет значение xhr. У кого-нибудь есть более простое решение, чем это?
Обновление: Решение выше было написано еще в Rails 1.x и больше не поддерживается.
15 ответов
Вы также можете хранить флэш-сообщения в заголовках ответов, используя блок after_filter, и отображать их, используя 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
А в application.js добавьте глобальный обработчик ajax. Для jquery сделайте что-то вроде этого:
$(document).ajaxError(function(event, request) {
var msg = request.getResponseHeader('X-Message');
if (msg) alert(msg);
});
Замените alert() вашей собственной функцией flash javascript или попробуйте jGrowl.
А вот моя версия, основанная на @emzero, с модификациями для работы с jQuery, протестирована на 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
});
макет: application.html.haml
#flash-message
- flash.each do |name, msg|
= content_tag :div, msg, :id => "flash_#{name}"
Я сделал это таким образом..
контроллер:
respond_to do |format| flash.now[:notice] = @msg / 'blah blah...' format.html format.js end
Посмотреть:
<div id='notice'>
<%= render :partial => 'layouts/flash' , :locals => { :flash => flash } %>
</div>
макеты / _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 %>");
Это необходимо в ответе JS
Если вы используете RSJ:
page.replace_html :notice, flash[:notice]
flash.discard
Если вы используете jQuery:
$("#flash_notice").html(<%=escape_javascript(flash.delete(:notice)) %>');
Основываясь на других -
(Мы передаем весь объект флэш-памяти как JSON, что позволяет нам реконструировать весь объект флэш-памяти в браузере. Это можно использовать для обеспечения отображения всех сообщений флэш-памяти в случае, если 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
}
Похоже, что вам нужно flash.now[:notice]
, который доступен только в текущем действии, а не в следующем. Вы можете посмотреть документацию здесь: http://api.rubyonrails.com/classes/ActionController/Flash/FlashHash.html
Назначьте сообщение в контроллере следующим образом:
flash.now[:notice] = 'Your message'
app / views / layouts / application.js.erb - макет для запросов Ajax. Здесь вы можете просто использовать
<%= yield %>
alert('<%= escape_javascript(flash.now[:notice]) %>');
или с некоторыми богатыми анимациями, используя gritter: http://boedesign.com/demos/gritter/
<%= yield %>
<% if flash.now[:notice] %>
$.gritter.add({
title: '--',
text: '<%= escape_javascript(flash.now[:notice]) %>'
});
<% end %>
Существует гем под названием Unobtrusive Flash, который автоматически кодирует флеш-сообщения в куки. Javascript на стороне клиента проверяет наличие флэш-памяти и отображает ее любым удобным для вас способом. Это работает без проблем в обычных и ajax-запросах.
На основании гудлейка ответ:
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
Затем в вашем application.js (если вы используете встроенные помощники Prototype в Rails) добавьте:
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
}
});
Я изменил ответ Виктора С, чтобы исправить некоторые случаи, когда flash[type].blank?
не работает, как отметили несколько человек в комментариях.
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
Тогда отдых такой же
// 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
});
Вот моя версия (работа с флеш-уведомлениями и спецсимволами в кодировке UTF-8):
Внутри 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
Внутри моего кофе-скрипта (твиттер-версия):
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>")
Я создаю движок, который включает некоторое поведение для application_controller для отправки флеш-сообщения в заголовке ответа, как предлагают некоторые из вас, ребята.
Другим способом было бы обновить / отобразить div "note" с сообщением от вашего обработчика запросов Ajax "OnFailure". Это дает вам возможность показывать эти флэш-сообщения с требуемым эффектом. Я использовал это
render:text => "Произошла какая-то ошибка",:status => 444
в JavaScript
новый AjaxRequest (... , OnFailure: функция (транспортная) {. $("# Извещение") обновление (transport.responseText); // показать сообщение } );
НТН
Единственное улучшение, о котором я могу подумать, - это сделать page.reload_flash по умолчанию (не нужно помещать его в каждый файл rjs, и сделать его недействительным, если вы не хотите перезагружать flash, что-то вроде page.keep_flash.
Я не знаю, с чего начать, но зная некоторые рельсы, я уверен, что это не так сложно.
В случае, если вы хотите использовать вызовы AJAX, redirect_to не должен использоваться в контроллере. Скорее, флэш-сообщение должно быть явно обозначено:
В вашем_контроллере:
respond_to :js
def your_ajax_method
flash[:notice] = 'Your message!'
end
В представлении, названном 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);
})
Пожалуйста, обратите внимание, что класс сообщения является точкой привязки для визуализации сообщений. Этот класс должен присутствовать в вашем представлении или макете приложения. Если вы используете ERB, строка становится $('.messages').html("<%= j(render 'layouts/messages') %>");
Приведенный выше JavaScript, встроенный в HAML/ERB, является ключом для отображения флэш-сообщений при использовании AJAX. Все остальные компоненты остаются неизменными для вызовов не AJAX.
Вы можете использовать your_ajax_method_in_the_controller.js.coffee
или просто.js, но тогда переменные rails не будут доступны для JS/Coffee. Несмотря на то, что здесь я не использую переменные, я предпочитаю оборачивать JS в HAML для обеспечения согласованности кодовой базы.
Я использую Twitter Bootstrap для оформления сообщений, таким образом $(".alert").alert('close')
исчезает уведомление. А вот сообщения частично:
макеты /_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}"
На всякий случай CSS для оповещений ниже
.alert-messages {
position: fixed;
top: 37px;
left: 30%;
right: 30%;
z-index: 7000;
}