heartcombo--simple_form/lib/simple_form/form_builder.rb

293 lines
11 KiB
Ruby
Raw Normal View History

2009-11-18 23:50:43 +00:00
module SimpleForm
class FormBuilder < ActionView::Helpers::FormBuilder
attr_reader :template, :object_name, :object, :reflection, :options
2009-12-09 18:55:39 +00:00
extend MapType
include SimpleForm::Inputs
map_type :password, :text, :file, :to => SimpleForm::Inputs::MappingInput
map_type :string, :email, :search, :tel, :url, :to => SimpleForm::Inputs::StringInput
map_type :integer, :decimal, :float, :to => SimpleForm::Inputs::NumericInput
map_type :select, :radio, :check_boxes, :to => SimpleForm::Inputs::CollectionInput
map_type :date, :time, :datetime, :to => SimpleForm::Inputs::DateTimeInput
map_type :country, :time_zone, :to => SimpleForm::Inputs::PriorityInput
map_type :boolean, :to => SimpleForm::Inputs::BooleanInput
2009-12-11 03:18:56 +00:00
# Basic input helper, combines all components in the stack to generate
# input html based on options the user define and some guesses through
2009-12-10 17:57:24 +00:00
# database column information. By default a call to input will generate
2009-12-11 03:18:56 +00:00
# label + input + hint (when defined) + errors (when exists), and all can
# be configured inside a wrapper html.
2009-12-10 21:52:23 +00:00
#
2009-12-11 11:19:46 +00:00
# == Examples
2009-12-10 21:52:23 +00:00
#
2009-12-10 17:57:24 +00:00
# # Imagine @user has error "can't be blank" on name
# simple_form_for @user do |f|
# f.input :name, :hint => 'My hint'
# end
#
2009-12-11 02:34:20 +00:00
# This is the output html (only the input portion, not the form):
#
# <label class="string required" for="user_name">
# <abbr title="required">*</abbr> Super User Name!
# </label>
# <input class="string required" id="user_name" maxlength="100"
# name="user[name]" size="100" type="text" value="Carlos" />
2009-12-10 17:57:24 +00:00
# <span class="hint">My hint</span>
# <span class="error">can't be blank</span>
#
# Each database type will render a default input, based on some mappings and
# heuristic to determine which is the best option.
2009-12-10 21:52:23 +00:00
#
2009-12-10 17:57:24 +00:00
# You have some options for the input to enable/disable some functions:
#
2009-12-10 21:52:23 +00:00
# :as => allows you to define the input type you want, for instance you
# can use it to generate a text field for a date column.
#
# :required => defines whether this attribute is required or not. True
# by default.
#
# The fact SimpleForm is built in components allow the interface to be unified.
# So, for instance, if you need to disable :hint for a given input, you can pass
# :hint => false. The same works for :error, :label and :wrapper.
#
# Besides the html for any component can be changed. So, if you want to change
# the label html you just need to give a hash to :label_html. To configure the
# input html, supply :input_html instead and so on.
#
# == Options
#
# Some inputs, as datetime, time and select allow you to give extra options, like
2009-12-11 02:34:20 +00:00
# prompt and/or include blank. Such options are given in plainly:
2009-12-10 21:52:23 +00:00
#
2009-12-11 02:34:20 +00:00
# f.input :created_at, :include_blank => true
2009-12-10 21:52:23 +00:00
#
# == Collection
#
# When playing with collections (:radio and :select inputs), you have three extra
# options:
#
# :collection => use to determine the collection to generate the radio or select
#
# :label_method => the method to apply on the array collection to get the label
#
# :value_method => the method to apply on the array collection to get the value
#
# == Priority
#
# Some inputs, as :time_zone and :country accepts a :priority option. If none is
# given SimpleForm.time_zone_priority and SimpleForm.country_priority are used respectivelly.
#
2010-01-10 04:12:57 +00:00
def input(attribute_name, options={}, &block)
column = find_attribute_column(attribute_name)
input_type = default_input_type(attribute_name, column, options)
2010-01-10 04:12:57 +00:00
if block_given?
SimpleForm::Inputs::BlockInput.new(self, block).render
2010-01-09 14:39:14 +00:00
else
2010-01-10 04:12:57 +00:00
klass = self.class.mappings[input_type] ||
self.class.const_get(:"#{input_type.to_s.camelize}Input")
klass.new(self, attribute_name, column, input_type, options).render
2010-01-09 14:39:14 +00:00
end
2009-11-19 21:26:16 +00:00
end
alias :attribute :input
2009-11-19 21:26:16 +00:00
2009-12-11 02:18:14 +00:00
# Helper for dealing with association selects/radios, generating the
2009-12-11 11:19:46 +00:00
# collection automatically. It's just a wrapper to input, so all options
# supported in input are also supported by association. Some extra options
# can also be given:
#
# == Examples
2009-12-11 02:18:14 +00:00
#
# simple_form_for @user do |f|
# f.association :company # Company.all
# end
#
# f.association :company, :collection => Company.all(:order => 'name')
# # Same as using :order option, but overriding collection
#
2010-01-10 04:12:57 +00:00
# == Block
#
# When a block is given, association simple behaves as a proxy to
# simple_fields_for:
#
# f.association :company do |c|
# c.input :name
# c.input :type
# end
#
# From the options above, only :collection can also be supplied.
#
def association(association, options={}, &block)
return simple_fields_for(*[association,
options.delete(:collection), options].compact, &block) if block_given?
2009-12-11 02:18:14 +00:00
raise ArgumentError, "Association cannot be used in forms not associated with an object" unless @object
options[:as] ||= :select
@reflection = find_association_reflection(association)
2009-12-14 10:37:03 +00:00
raise "Association #{association.inspect} not found" unless @reflection
case @reflection.macro
when :belongs_to
attribute = @reflection.options[:foreign_key] || :"#{@reflection.name}_id"
when :has_one
raise ":has_one association are not supported by f.association"
else
attribute = :"#{@reflection.name.to_s.singularize}_ids"
if options[:as] == :select
html_options = options[:input_html] ||= {}
html_options[:size] ||= 5
html_options[:multiple] = true unless html_options.key?(:multiple)
end
end
2009-12-11 02:18:14 +00:00
options[:collection] ||= @reflection.klass.all(@reflection.options.slice(:conditions, :order))
2009-12-11 02:18:14 +00:00
2010-07-27 09:48:07 +00:00
input(attribute, options).tap { @reflection = nil }
2009-12-11 02:18:14 +00:00
end
2009-12-10 20:03:27 +00:00
# Creates a button:
#
# form_for @user do |f|
# f.button :submit
# end
#
2010-02-06 20:44:07 +00:00
# It just acts as a proxy to method name given.
2009-12-10 20:03:27 +00:00
#
2010-02-06 20:44:07 +00:00
def button(type, *args, &block)
options = args.extract_options!
options[:class] = "button #{options[:class]}".strip
args << options
2010-02-06 20:44:07 +00:00
if respond_to?(:"#{type}_button")
send(:"#{type}_button", *args, &block)
else
send(type, *args, &block)
2009-12-10 20:03:27 +00:00
end
end
2009-12-10 17:57:24 +00:00
# Creates an error tag based on the given attribute, only when the attribute
2009-12-10 21:52:23 +00:00
# contains errors. All the given options are sent as :error_html.
#
# == Examples
#
# f.error :name
# f.error :name, :id => "cool_error"
2009-12-10 22:11:15 +00:00
#
def error(attribute_name, options={})
options[:error_html] = options
column = find_attribute_column(attribute_name)
input_type = default_input_type(attribute_name, column, options)
SimpleForm::Inputs::Base.new(self, attribute_name, column, input_type, options).error
end
2009-12-10 21:52:23 +00:00
# Creates a hint tag for the given attribute. Accepts a symbol indicating
# an attribute for I18n lookup or a string. All the given options are sent
# as :hint_html.
#
# == Examples
#
# f.hint :name # Do I18n lookup
# f.hint :name, :id => "cool_hint"
# f.hint "Don't forget to accept this"
#
def hint(attribute_name, options={})
options[:hint_html] = options
if attribute_name.is_a?(String)
options[:hint] = attribute_name
attribute_name, column, input_type = nil, nil, nil
else
column = find_attribute_column(attribute_name)
input_type = default_input_type(attribute_name, column, options)
end
SimpleForm::Inputs::Base.new(self, attribute_name, column, input_type, options).hint
end
2009-12-10 17:57:24 +00:00
# Creates a default label tag for the given attribute. You can give a label
2009-12-10 21:52:23 +00:00
# through the :label option or using i18n. All the given options are sent
# as :label_html.
#
# == Examples
#
# f.label :name # Do I18n lookup
# f.label :name, "Name" # Same behavior as Rails, do not add required tag
# f.label :name, :label => "Name" # Same as above, but adds required tag
#
# f.label :name, :required => false
# f.label :name, :id => "cool_label"
#
def label(attribute_name, *args)
2009-12-10 21:52:23 +00:00
return super if args.first.is_a?(String)
options = args.extract_options!
options[:label] = options.delete(:label)
options[:label_html] = options
options[:required] = options.delete(:required)
column = find_attribute_column(attribute_name)
input_type = default_input_type(attribute_name, column, options)
SimpleForm::Inputs::Base.new(self, attribute_name, column, input_type, options).label
end
# Creates an error notification message that only appears when the form object
# has some error. You can give a specific message with the :message option,
# otherwise it will look for a message using I18n. All other options given are
# passed straight as html options to the html tag.
#
# == Examples
#
# f.error_notification
# f.error_notification :message => 'Something went wrong'
# f.error_notification :id => 'user_error_message', :class => 'form_error'
#
def error_notification(options={})
SimpleForm::ErrorNotification.new(self, options).render
end
private
2009-12-10 17:57:24 +00:00
# Attempt to guess the better input type given the defined options. By
# default alwayls fallback to the user :as option, or to a :select when a
# collection is given.
def default_input_type(attribute_name, column, options) #:nodoc:
return options[:as].to_sym if options[:as]
return :select if options[:collection]
input_type = column.try(:type)
case input_type
when :timestamp
:datetime
when :string, nil
match = case attribute_name.to_s
2009-12-11 13:12:57 +00:00
when /password/ then :password
when /time_zone/ then :time_zone
when /country/ then :country
when /email/ then :email
when /phone/ then :tel
when /url/ then :url
2009-12-11 13:12:57 +00:00
end
match || input_type || file_method?(attribute_name) || :string
else
input_type
end
end
2009-12-08 19:08:36 +00:00
2009-12-11 13:12:57 +00:00
# Checks if attribute is a file_method.
def file_method?(attribute_name) #:nodoc:
file = @object.send(attribute_name) if @object.respond_to?(attribute_name)
2009-12-11 13:12:57 +00:00
:file if file && SimpleForm.file_methods.any? { |m| file.respond_to?(m) }
end
2009-12-10 17:57:24 +00:00
# Finds the database column for the given attribute
def find_attribute_column(attribute_name) #:nodoc:
@object.column_for_attribute(attribute_name) if @object.respond_to?(:column_for_attribute)
end
2009-12-14 10:37:03 +00:00
# Find reflection related to association
def find_association_reflection(association) #:nodoc:
@object.class.reflect_on_association(association) if @object.class.respond_to?(:reflect_on_association)
2009-12-11 02:18:14 +00:00
end
2009-11-18 23:50:43 +00:00
end
end