module Ajax module ActionController def self.included(klass) klass.class_eval do alias_method_chain :render, :ajax alias_method_chain :redirect_to_full_url, :ajax append_after_filter :process_response_headers end klass.extend(ClassMethods) end module ClassMethods # 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 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 protected # 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 into it. # # 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. def redirect_to_full_url_with_ajax(url, status) return redirect_to_full_url_without_ajax(url, status) unless Ajax.is_enabled? raise DoubleRenderError if performed? if url == request.headers["Referer"] && !request.headers['Ajax-Info'].blank? url = request.headers['Ajax-Info']['referer'] Ajax.logger.debug("[ajax] using referer #{url} from Ajax-Info") end if !Ajax.exclude_path?(url) && !Ajax.is_hashed_url?(url) url = Ajax.hashed_url_from_traditional(url) Ajax.logger.info("[ajax] rewrote redirect to #{url}") end session[:redirected_to] = url if request.xhr? render(:update) { |page| page.redirect_to(url) } else redirect_to_full_url_without_ajax(url, status) end end # Convert the Ajax-Info hash to JSON before the request is sent. def process_response_headers case response.headers['Ajax-Info'] when Hash response.headers['Ajax-Info'] = response.headers['Ajax-Info'].to_json 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? # Send the current layout and controller in a custom response header Ajax.set_header(response, :layout, ajax_layout) Ajax.set_header(response, :controller, self.class.controller_name) end render_without_ajax(options, extra_options, &block) end # Return the layout to use for an AJAX request, or the default layout if one # cannot be found. If no default is known, layouts/ajax/application is used. # # If no ajax_layout is set, look for the default layout in layouts/ajax. # If the layout cannot be found, use the default. # # FIXME: Use hard-coded html layout extension because default_template_format # is sometimes :js which means the layout isn't found. def layout_for_ajax(default) #:nodoc: ajax_layout = self.class.read_inheritable_attribute(:ajax_layout) if ajax_layout.nil? || !(ajax_layout =~ /^layouts\/ajax/) find_layout("layouts/ajax/#{default.sub(/layouts(\/)?/, '')}", 'html') unless default.nil? else ajax_layout end rescue ::ActionView::MissingTemplate Ajax.logger.info("[ajax] no layout found in layouts/ajax using #{default}") default end end end