Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
tree: eb2607ec9e
Fetching contributors…

Cannot retrieve contributors at this time

134 lines (118 sloc) 6.398 kb
module AttributeFu
# Methods for building forms that contain fields for associated models.
#
# Refer to the Conventions section in the README for the various expected defaults.
#
module AssociatedFormHelper
# Works similarly to fields_for, but used for building forms for associated objects.
#
# Automatically names fields to be compatible with the association_attributes= created by attribute_fu.
#
# An options hash can be specified to override the default behaviors.
#
# Options are:
# <tt>:javascript</tt> - Generate id placeholders for use with Prototype's Template class (this is how attribute_fu's add_associated_link works).
# <tt>:name</tt> - Specify the singular name of the association (in singular form), if it differs from the class name of the object.
#
# Any other supplied parameters are passed along to fields_for.
#
# Note: It is preferable to call render_associated_form, which will automatically wrap your form partial in a fields_for_associated call.
#
def fields_for_associated(associated, *args, &block)
conf = args.last.is_a?(Hash) ? args.last : {}
associated_name = (conf.delete(:name) || associated.class.name.underscore).to_s
name = associated_base_name associated_name
unless associated.new_record?
name << "[#{associated.new_record? ? 'new' : associated.id}]"
else
@new_objects ||= {}
@new_objects[associated_name] ||= -1 # we want naming to start at 0
identifier = !conf.nil? && conf[:javascript] ? '#{number}' : @new_objects[associated_name]+=1
name << "[new][#{identifier}]"
end
@template.fields_for(name, *args.unshift(associated), &block)
end
# Creates a link for removing an associated element from the form, by removing its containing element from the DOM.
#
# Must be called from within an associated form.
#
# An options hash can be specified to override the default behaviors.
#
# Options are:
# * <tt>:selector</tt> - The CSS selector with which to find the element to remove.
# * <tt>:function</tt> - Additional javascript to be executed before the element is removed.
#
# Any remaining options are passed along to link_to_function
#
def remove_link(name, *args)
options = args.extract_options!
css_selector = options.delete(:selector) || ".#{@object.class.name.underscore}"
function = options.delete(:function) || ""
function << "$(this).up(&quot;#{css_selector}&quot;).remove()"
@template.link_to_function(name, function, *args.push(options))
end
# Creates a link that adds a new associated form to the page using Javascript.
#
# Must be called from within an associated form.
#
# Must be provided with a new instance of the associated object.
#
# e.g. f.add_associated_link 'Add Task', @project.tasks.build
#
# An options hash can be specified to override the default behaviors.
#
# Options are:
# * <tt>:partial</tt> - specify the name of the partial in which the form is located.
# * <tt>:container</tt> - specify the DOM id of the container in which to insert the new element.
# * <tt>:name</tt> - specify an alternate class name for the associated model (underscored)
#
# Any additional options are forwarded to link_to_function. See its documentation for available options.
#
def add_associated_link(name, object, opts = {})
associated_name = opts.delete(:name) || object.class.name.underscore
variable = "attribute_fu_#{associated_name}_count"
opts.symbolize_keys!
partial = opts.delete(:partial) || associated_name
container = opts.delete(:container) || associated_name.pluralize
form_builder = self # because the value of self changes in the block
@template.link_to_function(name, opts) do |page|
page << "if (typeof #{variable} == 'undefined') #{variable} = 0;"
page << "new Insertion.Bottom('#{container}', new Template("+form_builder.render_associated_form(object, :fields_for => { :javascript => true }, :partial => partial).to_json+").evaluate({'number': --#{variable}}))"
end
end
# Renders the form of an associated object, wrapping it in a fields_for_associated call.
#
# The associated argument can be either an object, or a collection of objects to be rendered.
#
# An options hash can be specified to override the default behaviors.
#
# Options are:
# * <tt>:new</tt> - specify a certain number of new elements to be added to the form. Useful for displaying a
# few blank elements at the bottom.
# * <tt>:name</tt> - override the name of the association, both for the field names, and the name of the partial
# * <tt>:partial</tt> - specify the name of the partial in which the form is located.
# * <tt>:fields_for</tt> - specify additional options for the fields_for_associated call
# * <tt>:locals</tt> - specify additional variables to be passed along to the partial
# * <tt>:render</tt> - specify additional options to be passed along to the render :partial call
#
def render_associated_form(associated, opts = {})
associated = associated.is_a?(Array) ? associated : [associated] # preserve association proxy if this is one
opts.symbolize_keys!
(opts[:new] - associated.select(&:new_record?).length).times { associated.build } if opts[:new]
unless associated.empty?
name = (opts[:name] || associated.first.class.name.underscore).to_s
partial = opts[:partial] || name
local_assign_name = partial.split('/').last.split('.').first
associated.map do |element|
fields_for_associated(element, (opts[:fields_for] || {}).merge(:name => name)) do |f|
@template.render({:partial => "#{partial}", :locals => {local_assign_name.to_sym => element, :f => f}.merge(opts[:locals] || {})}.merge(opts[:render] || {}))
end
end
end
end
private
def associated_base_name(associated_name)
"#{@object_name}[#{associated_name}_attributes]"
end
end
end
Jump to Line
Something went wrong with that request. Please try again.