# All remote actions are automatically enabled to show a gui indicator during a remote call with this plugin. # # == Note # # If you have installed another plugin that overrides rails methods such as remote_function, # form_remote_tag, or submit_to_remote then this plugin may not work as intended. So if # you find that some methods aren't working as expected then this could be a possible problem. This also # holds for the other plugins overriding the same methods. # Default values that can be overridden in environment.rb # # * default_image - The default image indicator # * default_id - The default css id given to the indicator # * default_class - The default css class given to the indicator # * enable_all - Enable remote indicators on all remote functions by default # * effect - The default effect to apply to the indicator when it is shown(before) and hidden(after) # # == Examples # # * RemoteIndicator.default_image = 'spinner.gif' # * RemoteIndicator.default_id = 'spinner' # * RemoteIndicator.default_class = 'spinner' # * RemoteIndicator.enable_all = false # * RemoteIndicator.effect = {:before => 'new Effect.Appear', :after => 'new Effect.Fade'} class RemoteIndicator @@default_image = 'indicator.gif' cattr_accessor :default_image @@default_id = 'indicator' cattr_accessor :default_id @@default_class = 'indicator' cattr_accessor :default_class @@enable_all = true cattr_accessor :enable_all @@effect = {:before => 'Element.show', :after => 'Element.hide'} cattr_accessor :effect end # Progress indication is built in using the indicator method and the optional :indicator option for remote calls. # See remote_function documentation for more information. # # The :indicator options adds the functionality of using a remote indicator (an image) during the execution # of all remote functions. # # Functionality added to disable the form by default during a remote call using methods such as remote_function, # form_remote_tag, remote_form_for and submit_to_remote. # # This is useful to prevent a user from submitting a form twice while a remote call is in progress # since the submit button will be disabled and therefore not clickable. # # Additional options: # * :indicator - The css id of an element to show and hide during a remote call. # Defaults to RemoteIndicator.default_id. # Set :indicator to false if no indicator is to be used. # Set :indicator to true in order to use the default indicator (RemoteIndicator.default_id). # If RemoteIndicator.enable_all is set to true, :indicator => true is not required. # Set :indicator to a hash with the :toggle option to replace the current element with the dom element specified by :toggle. Ex. :indicator => {:toggle => dom_id(object)} # * :disable_form - Specifies if the form will disable or not during the remote call. # Defaults to true. # Set :disable_form to false to keep the form enabled during a remote function call. # * :before_effect - Specifies the before 'effect' for the indicator. # Defaults to 'Element.show'. # * :after_effect - Specifies the after 'effect' for the indicator. # Defaults to 'Element.hide'. module ActionView::Helpers::PrototypeHelper # Creates an indicator image. The options supplied are the same used with +image_tag+ # # === Examples # # Using a custom indicator id # <%= indicator :id => 'spinner' %> # # Shorthand using a string for the options (sets the :id automatically) # <%= indicator 'spinner' %> # # Toggle the current link with an indicator # <%= link_to_remote image_tag('add.png'), :url => do_something_path, :indicator => {:toggle => 'spinner'} %> <%= indicator 'spinner' %> # # Using many indicators on the same page # <% collection.each do |id| %> # <%= link_to_remote :url => do_something_path, :indicator => "link#{id}" %> <%= indicator :id => "link#{id}" %> # <% end %> def indicator(options = {}) indicator_image indicator_options(options) end # Sets the proper options for custom indicators. # # Current and additional options are: # * :id - The css id of the indicator. Defaults to RemoteIndicator.default_id # * :class - The css class of the indicator. Defaults to RemoteIndicator.default_class # * :hide - Hide the image by default. Defaults to true. # # === Example # # Create an indicator with text # <%= content_tag 'span', 'Updating Data... ', indicator_options %> # # Pass any options you'd normally use to the method itself # <%= content_tag 'span', 'Updating Data... ', indicator_options(:style => 'width:100px') def indicator_options(options = {}) # if options is a string then use it as the :id value options = {:id => options} if options.is_a?(String) options.reverse_merge!(:id => RemoteIndicator.default_id, :class => RemoteIndicator.default_class, :hide => true) options[:style] = [options[:style], 'display:none'].compact.join(';') if options.delete(:hide) options end # Creates an indicator image. The options supplied are the same used with +image_tag+ # # This method differs from +indicator+ in that it simply produces the indicator image without # the additional indicator options to for hiding the image by default etc. Without the # additional options, this method can create the standalone indicator image for use in more # complex indicators that use the image in combination with additional markup. # # === Example # # <%= content_tag 'div', 'Updating Data... ' + indicator_image, indicator_options %> def indicator_image(options = {}) image_tag RemoteIndicator.default_image, options end alias :remote_function_old :remote_function # === Examples # # * To use the default values - <%= remote_function :url => {:action => 'dosomething'} %> <%= indicator %> # * To disable the gui indicator - <%= remote_function :url => {:action => 'dosomething'}, :indicator => false %> # * To use a custom :id - <%= remote_function :url => {:action => 'dosomething'}, :indicator => 'custom' %> <%= indicator :id => 'custom' %> # * To fade the indicator instead of simply hiding it - <%= remote_function(:update => 'someid', :url => {:action => 'dosomething'}, :after_effect => 'new Effect.Fade') %> # # === Examples using :disable_form # See module documentation for usage of the :disable_form option. # # Automatically disables a form (no additional options) # <%= remote_function(:update => 'someid', :submit => 'myform', :url => {:action => 'dosomething'}) %> # # Prevents a form from being disabled # <%= remote_function(:update => 'someid', :submit => 'myform', :url => {:action => 'dosomething'}, :disable_form => false) %> # # No form disabled as no :submit option is provided # <%= remote_function(:update => 'someid', :url => {:action => 'dosomething'}) %> # # ==== IMPORTANT GOTCHA # Don't forget to use <%= indicator %> on the page with remote calls or the javascript will fail # and your action won't complete (In development mode you will be shown an alert if the indicator has not been defined). # This doesn't apply if you set the indicator to false... :indicator => false. def remote_function(options) options[:indicator] = RemoteIndicator.default_id if options[:indicator] == true || (options[:indicator].nil? && RemoteIndicator.enable_all) if indicator = options.delete(:indicator) options.reverse_merge! :before_effect => RemoteIndicator.effect[:before], :after_effect => RemoteIndicator.effect[:after] indicator = indicator[:toggle] if (toggle = indicator.is_a?(Hash)) before_js = String.new.tap do |js| js << "Element.hide(this);" if toggle js << "#{options[:before_effect]}('#{indicator}')" end after_js = String.new.tap do |js| js << "#{options[:after_effect]}('#{indicator}')" js << ";Element.show(this)" if toggle end event_options = { :before => (RAILS_ENV == 'development' ? "try { #{before_js} } catch(e) { alert('The remote helper indicator \\'#{indicator}\\' has not been defined.\\n\\nEither define the indicator with the \\'indicator\\' method or pass :indicator => false as an option to disable the indicator.') }" : before_js), :complete => after_js } merge_option_values! options, event_options end add_disable_options! options remote_function_old(options) end alias :form_remote_tag_old :form_remote_tag # See module documentation for usage of the :disable_form option. # See remote_function documentation for usage of the :indicator option. # # === Examples # # Automatically disables a form (no additional options) # <%= form_remote_tag(:url => {:action => 'dosomething'}) %> # # Prevents the form from being disabled # <%= form_remote_tag(:url => {:action => 'dosomething'}, :disable_form => false) %> def form_remote_tag(options = {}, &block) options.reverse_merge! :disable_form => true if options.delete(:disable_form) # we can't disable using :before because disabled fields aren't serialized, must use :after merge_option_values! options, { :before => 'var form = $(this), disabled_elems = []', # save form elements that are already disabled :after => "disabled_elems = form.select(':disabled'); Form.disable(form)", # re-disable any previously disabled elements :complete => "Form.enable(form); disabled_elems.invoke('disable')" } end form_remote_tag_old(options, &block) end alias :submit_to_remote_old :submit_to_remote # See module documentation for usage of the :disable_form option. # See remote_function documentation for usage of the :indicator option. # # The option :disable_form disables a form (specified using the :submit option) # # === Example # # Automatically disables a form (no additional options) # <%= submit_to_remote('name', 'Submit', :submit => 'myform', :url => {:action => 'dosomething'}) %> # # Prevents a form from being disabled # <%= submit_to_remote('name', 'Submit', :submit => 'myform', :url => {:action => 'dosomething'}, :disable_form => false) %> def submit_to_remote(name, value, options = {}) add_disable_options! options submit_to_remote_old(name, value, options) end private def add_disable_options!(options) options.reverse_merge! :disable_form => true if options.delete(:disable_form) && options[:submit] # we can't disable using :before because disabled fields aren't serialized, must use :after merge_option_values! options, {:after => "Form.disable('#{options[:submit]}')", :complete => "Form.enable('#{options[:submit]}')"} end end def merge_option_values!(options, code, sep = ';') code.each_pair {|key,value| options[key] = [value, options[key]].compact.join(sep)} end end