require 'set' require 'active_support/json' require 'active_support/core_ext/object/blank' module ActionView # = Action View Prototype Helpers module Helpers # Prototype[http://www.prototypejs.org/] is a JavaScript library that provides # DOM[http://en.wikipedia.org/wiki/Document_Object_Model] manipulation, # Ajax[http://www.adaptivepath.com/publications/essays/archives/000385.php] # functionality, and more traditional object-oriented facilities for JavaScript. # This module provides a set of helpers to make it more convenient to call # functions from Prototype using Rails, including functionality to call remote # Rails methods (that is, making a background request to a Rails action) using Ajax. # This means that you can call actions in your controllers without # reloading the page, but still update certain parts of it using # injections into the DOM. A common use case is having a form that adds # a new element to a list without reloading the page or updating a shopping # cart total when a new item is added. # # == Usage # To be able to use these helpers, you must first include the Prototype # JavaScript framework in your pages. # # javascript_include_tag 'prototype' # # (See the documentation for # ActionView::Helpers::JavaScriptHelper for more information on including # this and other JavaScript files in your Rails templates.) # # Now you're ready to call a remote action either through a link... # # link_to_remote "Add to cart", # :url => { :action => "add", :id => product.id }, # :update => { :success => "cart", :failure => "error" } # # ...through a form... # # <%= form_remote_tag :url => '/shipping' do -%> #
<%= submit_tag 'Recalculate Shipping' %>
# <% end -%> # # As you can see, there are numerous ways to use Prototype's Ajax functions (and actually more than # are listed here); check out the documentation for each method to find out more about its usage and options. # # === Common Options # See link_to_remote for documentation of options common to all Ajax # helpers; any of the options specified by link_to_remote can be used # by the other helpers. # # == Designing your Rails actions for Ajax # When building your action handlers (that is, the Rails actions that receive your background requests), it's # important to remember a few things. First, whatever your action would normally return to the browser, it will # return to the Ajax call. As such, you typically don't want to render with a layout. This call will cause # the layout to be transmitted back to your page, and, if you have a full HTML/CSS, will likely mess a lot of things up. # You can turn the layout off on particular actions by doing the following: # # class SiteController < ActionController::Base # layout "standard", :except => [:ajax_method, :more_ajax, :another_ajax] # end # # Optionally, you could do this in the method you wish to lack a layout: # # render :layout => false # # You can tell the type of request from within your action using the request.xhr? (XmlHttpRequest, the # method that Ajax uses to make background requests) method. # def name # # Is this an XmlHttpRequest request? # if (request.xhr?) # render :text => @name.to_s # else # # No? Then render an action. # render :action => 'view_attribute', :attr => @name # end # end # # The else clause can be left off and the current action will render with full layout and template. An extension # to this solution was posted to Ryan Heneise's blog at ArtOfMission["http://www.artofmission.com/"]. # # layout proc{ |c| c.request.xhr? ? false : "application" } # # Dropping this in your ApplicationController turns the layout off for every request that is an "xhr" request. # # If you are just returning a little data or don't want to build a template for your output, you may opt to simply # render text output, like this: # # render :text => 'Return this from my method!' # # Since whatever the method returns is injected into the DOM, this will simply inject some text (or HTML, if you # tell it to). This is usually how small updates, such updating a cart total or a file count, are handled. # # == Updating multiple elements # See JavaScriptGenerator for information on updating multiple elements # on the page in an Ajax response. module PrototypeHelper CALLBACKS = Set.new([ :create, :uninitialized, :loading, :loaded, :interactive, :complete, :failure, :success ] + (100..599).to_a) AJAX_OPTIONS = Set.new([ :before, :after, :condition, :url, :asynchronous, :method, :insertion, :position, :form, :with, :update, :script, :type ]).merge(CALLBACKS) # Returns the JavaScript needed for a remote function. # Takes the same arguments as link_to_remote. # # Example: # # Generates: { :action => :update_options }) %>"> # # # def remote_function(options) javascript_options = options_for_ajax(options) update = '' if options[:update] && options[:update].is_a?(Hash) update = [] update << "success:'#{options[:update][:success]}'" if options[:update][:success] update << "failure:'#{options[:update][:failure]}'" if options[:update][:failure] update = '{' + update.join(',') + '}' elsif options[:update] update << "'#{options[:update]}'" end function = update.empty? ? "new Ajax.Request(" : "new Ajax.Updater(#{update}, " url_options = options[:url] url_options = url_options.merge(:escape => false) if url_options.is_a?(Hash) function << "'#{html_escape(escape_javascript(url_for(url_options)))}'" function << ", #{javascript_options})" function = "#{options[:before]}; #{function}" if options[:before] function = "#{function}; #{options[:after]}" if options[:after] function = "if (#{options[:condition]}) { #{function}; }" if options[:condition] function = "if (confirm('#{escape_javascript(options[:confirm])}')) { #{function}; }" if options[:confirm] return function end # All the methods were moved to GeneratorMethods so that # #include_helpers_from_context has nothing to overwrite. class JavaScriptGenerator #:nodoc: def initialize(context, &block) #:nodoc: @context, @lines = context, [] include_helpers_from_context @context.with_output_buffer(@lines) do @context.instance_exec(self, &block) end end private def include_helpers_from_context extend @context.helpers if @context.respond_to?(:helpers) extend GeneratorMethods end # JavaScriptGenerator generates blocks of JavaScript code that allow you # to change the content and presentation of multiple DOM elements. Use # this in your Ajax response bodies, either in a