module Ajax
module ActionController
def self.included(klass)
klass.class_eval do
extend MirrorMethods
include MirrorMethods
after_filter :serialize_ajax_info
include(Ajax.app.rails?(3) ? Rails3 : Rails2)
end
end
module MirrorMethods
# Set a custom response header if the request is AJAX.
#
# Call with key and optional value. Pass a
# block to yield a dynamic value.
#
# Accepts :only and :except conditions because we create
# an after_filter.
def ajax_header(*args, &block)
return unless Ajax.is_enabled?
options = args.extract_options!
key, value = args.shift, args.shift
value = block_given? ? Proc.new : value
(self.is_a?(Class) ? self : self.class).prepend_after_filter(options) do |controller|
if controller.request.xhr?
value = value.is_a?(Proc) ? controller.instance_eval(&value) : value
Ajax.set_header(controller.response, key, value)
end
end
end
# Set the layout to use for AJAX requests.
#
# By default we look in layouts/ajax/ for this controllers default
# layout and render that. If it can't be found, the default layout
# is used.
def ajax_layout(template_name)
write_inheritable_attribute(:ajax_layout, template_name)
end
end
module Rails2
def self.included(klass)
klass.class_eval do
alias_method_chain :redirect_to_full_url, :ajax
alias_method_chain :render, :ajax
end
end
# Redirect to hashed URLs unless the path is excepted.
#
# Store the URL that we are redirecting to in the session.
# If we then have a request for the root URL we know
# to render this URL instead.
#
# If redirecting back to the referer, use the referer
# in the Ajax-Info header because it includes the
# hashed part of the URL. Otherwise the referer is
# always the root url.
#
# For AJAX requests, respond with an AJAX-suitable
# redirect.
#
# This method only applies to Rails < 3
def redirect_to_full_url_with_ajax(url, status)
if !_ajax_redirect(url, status)
redirect_to_full_url_without_ajax(url, status)
end
end
#
# Intercept rendering to customize the headers and layout handling
#
def render_with_ajax(options = nil, extra_options = {}, &block)
return render_without_ajax(options, extra_options, &block) unless Ajax.is_enabled?
original_args = [options, extra_options]
if request.xhr?
# Options processing taken from ActionController::Base#render
if options.nil?
options = { :template => default_template, :layout => true }
elsif options == :update
options = extra_options.merge({ :update => true })
elsif options.is_a?(String) || options.is_a?(Symbol)
case options.to_s.index('/')
when 0
extra_options[:file] = options
when nil
extra_options[:action] = options
else
extra_options[:template] = options
end
options = extra_options
elsif !options.is_a?(Hash)
extra_options[:partial] = options
options = extra_options
end
default = pick_layout(options)
default = default.path_without_format_and_extension unless default.nil?
ajax_layout = _layout_for_ajax(default)
ajax_layout = ajax_layout.path_without_format_and_extension unless ajax_layout.nil?
options[:layout] = ajax_layout unless ajax_layout.nil?
end
render_without_ajax(options, extra_options, &block)
end
end
module Rails3
# Rails 3 hook. Rails < 3 is handled using redirect_to_full_url. See
# those docs for info.
def redirect_to(url={}, status={})
if !_ajax_redirect(url, status)
super
end
end
# Specialize the default layout finder. Try to use a layout in layouts/ajax
# as the default layout, if one exists.
def _default_layout(require_layout = false)
if require_layout || !request.xhr? || !Ajax.is_enabled?
super
else
layout_name = super
if ajax_layout = _layout_for_ajax(layout_name)
ajax_layout.virtual_path
else
layout_name
end
end
end
end
protected
# Convert the Ajax-Info hash to JSON before the request is sent.
# Invoked as an after filter.
#
# Adds the current +layout+ and +controller+ to the hash.
# These values will be sent in future requests using the Ajax-Info header.
#
# +controller+ is the result of calling ActionController#controller_name, so
# if your controller is ApplicationController the value will be 'application'.
def serialize_ajax_info
Ajax.set_header(response, :layout, Ajax.app.rails?(3) ? _layout : active_layout)
Ajax.set_header(response, :controller, self.class.controller_name)
response.headers['Ajax-Info'] = Ajax.send(:serialize_hash, response.headers['Ajax-Info'])
end
# Perform special processing on the response if we need to.
# Return true if an Ajax "redirect" was performed, and false
# otherwise.
def _ajax_redirect(url, status)
return false if url.nil? || !Ajax.is_enabled?
special_redirect = false
original_url = url
# If we have the full referrer in Ajax-Info, use that because it
# includes the fragment.
if url == request.headers["Referer"] && !request.headers['Ajax-Info'].blank? && !request.headers['Ajax-Info']['referer'].blank?
url = request.headers['Ajax-Info']['referer']
Ajax.logger.debug("[ajax] using referer #{url} from Ajax-Info")
end
if !Ajax.exclude_path?(url)
# Never redirect to the Ajax framework path, redirect to /
if url =~ %r[#{Ajax.framework_path}]
url = url.sub(%r[#{Ajax.framework_path}], '/')
# Special case:
#
# Changing protocol forces a redirect from root to root.
# The full request URL (including the hashed part) is
# in the browser. So return JS to do the redirect and
# have it include the hashed part in the redirect URL.
if !request.xhr? && URI.parse(url).scheme != URI.parse(request.url).scheme
special_redirect = true
end
end
if !Ajax.is_hashed_url?(url) and !Ajax.is_robot?(request.user_agent)
url = Ajax.hashed_url_from_traditional(url)
end
end
Ajax.logger.info("[ajax] rewrote redirect from #{original_url} to #{url}") unless original_url == url
# Don't store session[:redirected_to] if doing a special redirect otherwise
# when the next request for root comes in it will think we really want
# to display the home page.
if special_redirect
session[:redirected_to] = nil
Ajax.logger.info("[ajax] returning special redirect JS")
render :layout => false, :text => <<-END
END
else
session[:redirected_to] = url
if request.xhr? && Ajax.is_hashed_url?(url)
Ajax.logger.info("[ajax] detecting we are xhr. soft redirect")
redirect_path = URI.parse(url).select(:fragment).first
Ajax.logger.info("[ajax] redirect path is #{redirect_path}")
Ajax.set_header(response, :soft_redirect, redirect_path)
render :layout => false, :text => <<-END
END
else
Ajax.logger.info("[ajax] not detecting we are xhr. Hard redirect!")
return false
end
end
true
end
# Return the layout to use for an AJAX request, or nil if the default should be used.
#
# If no ajax_layout is set, look for the default layout in layouts/ajax.
# If the layout cannot be found, use the default.
def _layout_for_ajax(default) #:nodoc:
ajax_layout = self.class.read_inheritable_attribute(:ajax_layout)
ajax_layout = if ajax_layout.nil? && default.nil?
nil
elsif ajax_layout.nil? && !default.nil? # look for one with the default name in layouts/ajax
"layouts/ajax/#{default.sub(/layouts(\/)?/, '')}"
elsif ajax_layout && !(ajax_layout =~ /^layouts\/ajax/) # look for it in layouts/ajax
"layouts/ajax/#{ajax_layout}"
else # look as is
ajax_layout
end
Ajax.app.rails?(3) ? find_template(ajax_layout) : find_layout(ajax_layout, 'html') if !ajax_layout.nil?
rescue ::ActionView::MissingTemplate
Ajax.logger.info("[ajax] no layout found in layouts/ajax. Using #{default}.")
nil
end
end
end