lib/formtastic/helpers/fieldset_wrapper.rb
# frozen_string_literal: true
module Formtastic
module Helpers
# @private
module FieldsetWrapper
protected
# Generates a fieldset and wraps the content in an ordered list. When working
# with nested attributes, it allows %i as interpolation option in :name. So you can do:
#
# f.inputs :name => 'Task #%i', :for => :tasks
#
# or the shorter equivalent:
#
# f.inputs 'Task #%i', :for => :tasks
#
# And it will generate a fieldset for each task with legend 'Task #1', 'Task #2',
# 'Task #3' and so on.
#
# Note: Special case for the inline inputs (non-block):
# f.inputs "My little legend", :title, :body, :author # Explicit legend string => "My little legend"
# f.inputs :my_little_legend, :title, :body, :author # Localized (118n) legend with I18n key => I18n.t(:my_little_legend, ...)
# f.inputs :title, :body, :author # First argument is a column => (no legend)
def field_set_and_list_wrapping(*args, &block) # @private
contents = args[-1].is_a?(::Hash) ? '' : args.pop.flatten
html_options = args.extract_options!
if block_given?
contents = if template.respond_to?(:is_haml?) && template.is_haml?
template.capture_haml(&block)
else
template.capture(&block)
end
end
# Work-around for empty contents block
contents ||= ""
# Ruby 1.9: String#to_s behavior changed, need to make an explicit join.
contents = contents.join if contents.respond_to?(:join)
legend = field_set_legend(html_options)
fieldset = template.content_tag(:fieldset,
legend.html_safe << template.content_tag(:ol, contents.html_safe),
html_options.except(:builder, :parent, :name)
)
fieldset
end
def field_set_legend(html_options)
legend = (html_options[:name] || '').to_s
# only applying if String includes '%i' avoids argument error when $DEBUG is true
legend %= parent_child_index(html_options[:parent]) if html_options[:parent] && legend.include?('%i')
legend = template.content_tag(:legend, template.content_tag(:span, legend.html_safe)) unless legend.blank?
legend
end
# Gets the nested_child_index value from the parent builder. It returns a hash with each
# association that the parent builds.
def parent_child_index(parent) # @private
# Could be {"post[authors_attributes]"=>0} or { :authors => 0 }
duck = parent[:builder].instance_variable_get('@nested_child_index')
# Could be symbol for the association, or a model (or an array of either, I think? TODO)
child = parent[:for]
# Pull a sybol or model out of Array (TODO: check if there's an Array)
child = child.first if child.respond_to?(:first)
# If it's an object, get a symbol from the class name
child = child.class.name.underscore.to_sym unless child.is_a?(Symbol)
key = "#{parent[:builder].object_name}[#{child}_attributes]"
# TODO: One of the tests produces a scenario where duck is "0" and the test looks for a "1"
# in the legend, so if we have a number, return it with a +1 until we can verify this scenario.
return duck + 1 if duck.is_a?(Integer)
# First try to extract key from duck Hash, then try child
(duck[key] || duck[child]).to_i + 1
end
end
end
end