require File.dirname(__FILE__) + '/tag_helper' module ActionView module Helpers # You must call <%= define_javascript_functions %> in your application, # or copy the included Javascript libraries into your application's # public/javascripts/ directory, before using these helpers. module JavascriptHelper unless const_defined? :CALLBACKS CALLBACKS = [:uninitialized, :loading, :loaded, :interactive, :complete] JAVASCRIPT_PATH = File.join(File.dirname(__FILE__), 'javascripts') end # Returns a link that'll trigger a javascript +function+ using the # onclick handler and return false after the fact. # # Examples: # link_to_function "Greeting", "alert('Hello world!')" # link_to_function(image_tag("delete"), "if confirm('Really?'){ do_delete(); }") def link_to_function(name, function, html_options = {}) content_tag( "a", name, html_options.symbolize_keys.merge(:href => "#", :onclick => "#{function}; return false;") ) end # Returns a link to a remote action defined by options[:url] # (using the url_for format) that's called in the background using # XMLHttpRequest. The result of that request can then be inserted into a # DOM object whose id can be specified with options[:update]. # Usually, the result would be a partial prepared by the controller with # either render_partial or render_partial_collection. # # Examples: # link_to_remote "Delete this post", :update => "posts", :url => { :action => "destroy", :id => post.id } # link_to_remote(image_tag("refresh"), :update => "emails", :url => { :action => "list_emails" }) # # By default, these remote requests are processed asynchronous during # which various callbacks can be triggered (for progress indicators and # the likes). # # Example: # link_to_remote word, # :url => { :action => "undo", :n => word_counter }, # :complete => "undoRequestCompleted(request)" # # The complete list of callbacks that may be specified are: # # :uninitialized:: Called before the remote document is # initialized with data. # :loading:: Called when the remote document is being # loaded with data by the browser. # :loaded:: Called when the browser has finished loading # the remote document. # :interactive:: Called when the user can interact with the # remote document, even though it has not # finished loading. # :complete:: Called when the XMLHttpRequest is complete. # # If you for some reason or another need synchronous processing (that'll # block the browser while the request is happening), you can specify # options[:type] = :synchronous. def link_to_remote(name, options = {}, html_options = {}) link_to_function(name, remote_function(options), html_options) end def form_remote_tag(options = {}) options[:form] = true options[:html] ||= { } options[:html][:onsubmit] = "#{remote_function(options)}; return false;" tag("form", options[:html], true) end def remote_function(options) #:nodoc: for now javascript_options = options_for_ajax(options) function = options[:update] ? "new Ajax.Updater('#{options[:update]}', " : "new Ajax.Request(" function << "'#{url_for(options[:url])}'" 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] return function end # Includes the Action Pack Javascript library inside a single ' end # Observes the field with the DOM ID specified by +field_id+ and makes # an Ajax when its contents have changed. # # Required +options+ are: # :frequency:: The frequency (in seconds) at which changes to # this field will be detected. # :url:: +url_for+-style options for the action to call # when the field has changed. # # Additional options are: # :update:: Specifies the DOM ID of the element whose # innerHTML should be updated with the # XMLHttpRequest response text. # :with:: A Javascript expression specifying the # parameters for the XMLHttpRequest. This defaults # to 'value', which in the evaluated context # refers to the new field value. # # Additionally, you may specify any of the options documented in # +link_to_remote. def observe_field(field_id, options = {}) build_observer('Form.Element.Observer', field_id, options) end # Like +observe_field+, but operates on an entire form identified by the # DOM ID +form_id+. +options+ are the same as +observe_field+, except # the default value of the :with option evaluates to the # serialized (request string) value of the form. def observe_form(form_id, options = {}) build_observer('Form.Observer', form_id, options) end private def escape_javascript(javascript) (javascript || '').gsub('"', '\"') end def options_for_ajax(options) js_options = build_callbacks(options) js_options['asynchronous'] = options[:type] != :synchronous js_options['method'] = options[:method] if options[:method] if options[:form] js_options['parameters'] = 'Form.serialize(this)' elsif options[:with] js_options['parameters'] = options[:with] end '{' + js_options.map {|k, v| "#{k}:#{v}"}.join(', ') + '}' end def build_observer(klass, name, options = {}) options[:with] ||= 'value' if options[:update] callback = remote_function(options) javascript = '" end def build_callbacks(options) CALLBACKS.inject({}) do |callbacks, callback| name = 'on' + callback.to_s.capitalize code = escape_javascript(options[callback]) callbacks[name] = "function(request){#{code}}" if callbacks[name] callbacks end end end end end