2012-12-14 17:14:41 -05:00
|
|
|
require 'active_support/core_ext/object/deep_dup'
|
2013-01-02 12:02:41 -05:00
|
|
|
require 'simple_form/map_type'
|
2013-01-21 16:57:37 -05:00
|
|
|
require 'simple_form/tags'
|
2011-11-29 15:37:58 -05:00
|
|
|
|
2009-11-18 18:50:43 -05:00
|
|
|
module SimpleForm
|
|
|
|
class FormBuilder < ActionView::Helpers::FormBuilder
|
2011-09-04 05:31:24 -04:00
|
|
|
attr_reader :template, :object_name, :object, :wrapper
|
2009-12-09 13:55:39 -05:00
|
|
|
|
2011-11-08 06:59:34 -05:00
|
|
|
# When action is create or update, we still should use new and edit
|
|
|
|
ACTIONS = {
|
2013-01-28 16:02:59 -05:00
|
|
|
create: :new,
|
|
|
|
update: :edit
|
2011-11-08 06:59:34 -05:00
|
|
|
}
|
|
|
|
|
2013-05-16 14:39:14 -04:00
|
|
|
ATTRIBUTE_COMPONENTS = [:html5, :min_max, :maxlength, :placeholder, :pattern, :readonly]
|
|
|
|
|
2010-01-09 10:05:02 -05:00
|
|
|
extend MapType
|
|
|
|
include SimpleForm::Inputs
|
|
|
|
|
2013-01-28 16:02:59 -05:00
|
|
|
map_type :text, to: SimpleForm::Inputs::TextInput
|
|
|
|
map_type :file, to: SimpleForm::Inputs::FileInput
|
|
|
|
map_type :string, :email, :search, :tel, :url, to: SimpleForm::Inputs::StringInput
|
|
|
|
map_type :password, to: SimpleForm::Inputs::PasswordInput
|
|
|
|
map_type :integer, :decimal, :float, to: SimpleForm::Inputs::NumericInput
|
|
|
|
map_type :range, to: SimpleForm::Inputs::RangeInput
|
|
|
|
map_type :check_boxes, to: SimpleForm::Inputs::CollectionCheckBoxesInput
|
|
|
|
map_type :radio_buttons, to: SimpleForm::Inputs::CollectionRadioButtonsInput
|
|
|
|
map_type :select, to: SimpleForm::Inputs::CollectionSelectInput
|
|
|
|
map_type :grouped_select, to: SimpleForm::Inputs::GroupedCollectionSelectInput
|
|
|
|
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
|
2010-01-09 10:05:02 -05:00
|
|
|
|
2011-04-27 14:55:03 -04:00
|
|
|
def self.discovery_cache
|
|
|
|
@discovery_cache ||= {}
|
|
|
|
end
|
|
|
|
|
2011-09-04 05:31:24 -04:00
|
|
|
def initialize(*) #:nodoc:
|
|
|
|
super
|
2011-11-09 17:43:49 -05:00
|
|
|
@defaults = options[:defaults]
|
2012-01-24 13:54:28 -05:00
|
|
|
@wrapper = SimpleForm.wrapper(options[:wrapper] || SimpleForm.default_wrapper)
|
2011-09-04 05:31:24 -04:00
|
|
|
end
|
|
|
|
|
2009-12-10 22:18:56 -05: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 12:57:24 -05:00
|
|
|
# database column information. By default a call to input will generate
|
2009-12-10 22:18:56 -05:00
|
|
|
# label + input + hint (when defined) + errors (when exists), and all can
|
|
|
|
# be configured inside a wrapper html.
|
2009-12-10 16:52:23 -05:00
|
|
|
#
|
2009-12-11 06:19:46 -05:00
|
|
|
# == Examples
|
2009-12-10 16:52:23 -05:00
|
|
|
#
|
2009-12-10 12:57:24 -05:00
|
|
|
# # Imagine @user has error "can't be blank" on name
|
|
|
|
# simple_form_for @user do |f|
|
2013-01-28 16:02:59 -05:00
|
|
|
# f.input :name, hint: 'My hint'
|
2009-12-10 12:57:24 -05:00
|
|
|
# end
|
|
|
|
#
|
2009-12-10 21:34:20 -05:00
|
|
|
# This is the output html (only the input portion, not the form):
|
|
|
|
#
|
2009-12-10 17:23:29 -05:00
|
|
|
# <label class="string required" for="user_name">
|
|
|
|
# <abbr title="required">*</abbr> Super User Name!
|
|
|
|
# </label>
|
|
|
|
# <input class="string required" id="user_name" maxlength="100"
|
2012-12-27 16:39:32 -05:00
|
|
|
# name="user[name]" type="text" value="Carlos" />
|
2009-12-10 12:57:24 -05: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 16:52:23 -05:00
|
|
|
#
|
2009-12-10 12:57:24 -05:00
|
|
|
# You have some options for the input to enable/disable some functions:
|
|
|
|
#
|
2013-01-28 16:02:59 -05:00
|
|
|
# as: allows you to define the input type you want, for instance you
|
2009-12-10 16:52:23 -05:00
|
|
|
# can use it to generate a text field for a date column.
|
|
|
|
#
|
2013-01-28 16:02:59 -05:00
|
|
|
# required: defines whether this attribute is required or not. True
|
2009-12-10 16:52:23 -05:00
|
|
|
# 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
|
2013-01-28 16:02:59 -05:00
|
|
|
# hint: false. The same works for :error, :label and :wrapper.
|
2009-12-10 16:52:23 -05:00
|
|
|
#
|
|
|
|
# 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-10 21:34:20 -05:00
|
|
|
# prompt and/or include blank. Such options are given in plainly:
|
2009-12-10 16:52:23 -05:00
|
|
|
#
|
2013-01-28 16:02:59 -05:00
|
|
|
# f.input :created_at, include_blank: true
|
2009-12-10 16:52:23 -05:00
|
|
|
#
|
|
|
|
# == Collection
|
|
|
|
#
|
2012-01-27 13:30:19 -05:00
|
|
|
# When playing with collections (:radio_buttons, :check_boxes and :select
|
|
|
|
# inputs), you have three extra options:
|
2009-12-10 16:52:23 -05:00
|
|
|
#
|
2013-01-28 16:02:59 -05:00
|
|
|
# collection: use to determine the collection to generate the radio or select
|
2009-12-10 16:52:23 -05:00
|
|
|
#
|
2013-01-28 16:02:59 -05:00
|
|
|
# label_method: the method to apply on the array collection to get the label
|
2009-12-10 16:52:23 -05:00
|
|
|
#
|
2013-01-28 16:02:59 -05:00
|
|
|
# value_method: the method to apply on the array collection to get the value
|
2009-12-10 16:52:23 -05:00
|
|
|
#
|
2009-12-11 08:53:18 -05:00
|
|
|
# == Priority
|
|
|
|
#
|
|
|
|
# Some inputs, as :time_zone and :country accepts a :priority option. If none is
|
2012-04-29 10:57:01 -04:00
|
|
|
# given SimpleForm.time_zone_priority and SimpleForm.country_priority are used respectively.
|
2009-12-11 08:53:18 -05:00
|
|
|
#
|
2010-01-09 23:12:57 -05:00
|
|
|
def input(attribute_name, options={}, &block)
|
2011-11-29 15:37:58 -05:00
|
|
|
options = @defaults.deep_dup.deep_merge(options) if @defaults
|
2012-08-11 08:52:10 -04:00
|
|
|
input = find_input(attribute_name, options, &block)
|
2011-11-09 17:43:49 -05:00
|
|
|
|
2011-09-04 05:52:48 -04:00
|
|
|
chosen =
|
2012-08-11 08:52:10 -04:00
|
|
|
if name = options[:wrapper] || find_wrapper_mapping(input.input_type)
|
2011-09-26 21:47:06 -04:00
|
|
|
name.respond_to?(:render) ? name : SimpleForm.wrapper(name)
|
2011-09-04 05:52:48 -04:00
|
|
|
else
|
|
|
|
wrapper
|
|
|
|
end
|
|
|
|
|
2012-08-11 08:52:10 -04:00
|
|
|
chosen.render input
|
2009-11-19 16:26:16 -05:00
|
|
|
end
|
2009-12-13 16:16:00 -05:00
|
|
|
alias :attribute :input
|
2011-04-19 14:13:26 -04:00
|
|
|
|
2011-04-19 14:47:46 -04:00
|
|
|
# Creates a input tag for the given attribute. All the given options
|
|
|
|
# are sent as :input_html.
|
2011-04-19 11:53:49 -04:00
|
|
|
#
|
|
|
|
# == Examples
|
|
|
|
#
|
|
|
|
# simple_form_for @user do |f|
|
2011-04-19 14:16:10 -04:00
|
|
|
# f.input_field :name
|
2011-04-19 11:53:49 -04:00
|
|
|
# end
|
|
|
|
#
|
2011-04-19 14:23:24 -04:00
|
|
|
# This is the output html (only the input portion, not the form):
|
|
|
|
#
|
|
|
|
# <input class="string required" id="user_name" maxlength="100"
|
2012-12-27 16:39:32 -05:00
|
|
|
# name="user[name]" type="text" value="Carlos" />
|
2011-04-19 14:23:24 -04:00
|
|
|
#
|
2011-04-19 14:16:10 -04:00
|
|
|
def input_field(attribute_name, options={})
|
2012-02-20 22:42:06 -05:00
|
|
|
options = options.dup
|
2013-05-16 14:39:14 -04:00
|
|
|
options[:input_html] = options.except(:as, :collection, :label_method, :value_method, *ATTRIBUTE_COMPONENTS)
|
2012-12-26 00:46:51 -05:00
|
|
|
options = @defaults.deep_dup.deep_merge(options) if @defaults
|
|
|
|
|
2013-05-16 14:39:14 -04:00
|
|
|
SimpleForm::Wrappers::Root.new(ATTRIBUTE_COMPONENTS + [:input], wrapper: false).render find_input(attribute_name, options)
|
2011-04-19 11:53:49 -04:00
|
|
|
end
|
2009-11-19 16:26:16 -05:00
|
|
|
|
2009-12-10 21:18:14 -05:00
|
|
|
# Helper for dealing with association selects/radios, generating the
|
2009-12-11 06:19:46 -05: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-10 21:18:14 -05:00
|
|
|
#
|
|
|
|
# simple_form_for @user do |f|
|
|
|
|
# f.association :company # Company.all
|
|
|
|
# end
|
|
|
|
#
|
2013-01-28 16:02:59 -05:00
|
|
|
# f.association :company, collection: Company.all(order: 'name')
|
2009-12-10 21:18:14 -05:00
|
|
|
# # Same as using :order option, but overriding collection
|
|
|
|
#
|
2010-01-09 23:12:57 -05: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.
|
|
|
|
#
|
2013-07-03 11:11:36 -04:00
|
|
|
# Please note that the association helper is currently only tested with Active Record. Depending on the ORM you are using your mileage may vary.
|
|
|
|
#
|
2010-01-09 23:12:57 -05:00
|
|
|
def association(association, options={}, &block)
|
2012-02-20 22:42:06 -05:00
|
|
|
options = options.dup
|
|
|
|
|
2010-01-09 23:12:57 -05:00
|
|
|
return simple_fields_for(*[association,
|
|
|
|
options.delete(:collection), options].compact, &block) if block_given?
|
|
|
|
|
2009-12-10 21:18:14 -05:00
|
|
|
raise ArgumentError, "Association cannot be used in forms not associated with an object" unless @object
|
|
|
|
|
2010-12-07 21:25:14 -05:00
|
|
|
reflection = find_association_reflection(association)
|
|
|
|
raise "Association #{association.inspect} not found" unless reflection
|
|
|
|
|
2009-12-11 07:43:43 -05:00
|
|
|
options[:as] ||= :select
|
2012-06-22 17:23:55 -04:00
|
|
|
options[:collection] ||= options.fetch(:collection) {
|
2013-05-25 15:39:07 -04:00
|
|
|
conditions = reflection.options[:conditions]
|
2013-05-26 11:59:26 -04:00
|
|
|
conditions = conditions.call if conditions.respond_to?(:call)
|
2013-06-06 12:31:58 -04:00
|
|
|
reflection.klass.where(conditions).order(reflection.options[:order])
|
2012-06-22 17:23:55 -04:00
|
|
|
}
|
2009-12-10 21:34:26 -05:00
|
|
|
|
2010-12-07 21:25:14 -05:00
|
|
|
attribute = case reflection.macro
|
2009-12-11 07:43:43 -05:00
|
|
|
when :belongs_to
|
2012-01-25 09:08:46 -05:00
|
|
|
(reflection.respond_to?(:options) && reflection.options[:foreign_key]) || :"#{reflection.name}_id"
|
2009-12-11 07:43:43 -05:00
|
|
|
when :has_one
|
2012-09-13 16:07:31 -04:00
|
|
|
raise ArgumentError, ":has_one associations are not supported by f.association"
|
2009-12-11 07:43:43 -05:00
|
|
|
else
|
|
|
|
if options[:as] == :select
|
|
|
|
html_options = options[:input_html] ||= {}
|
|
|
|
html_options[:multiple] = true unless html_options.key?(:multiple)
|
|
|
|
end
|
2009-12-10 21:18:14 -05:00
|
|
|
|
2011-05-16 22:17:09 -04:00
|
|
|
# Force the association to be preloaded for performance.
|
|
|
|
if options[:preload] != false && object.respond_to?(association)
|
|
|
|
target = object.send(association)
|
|
|
|
target.to_a if target.respond_to?(:to_a)
|
|
|
|
end
|
2009-12-10 21:18:14 -05:00
|
|
|
|
2011-05-16 22:17:09 -04:00
|
|
|
:"#{reflection.name.to_s.singularize}_ids"
|
2011-04-29 15:23:42 -04:00
|
|
|
end
|
|
|
|
|
2013-01-28 16:02:59 -05:00
|
|
|
input(attribute, options.merge(reflection: reflection))
|
2009-12-10 21:18:14 -05:00
|
|
|
end
|
|
|
|
|
2009-12-10 15:03:27 -05:00
|
|
|
# Creates a button:
|
|
|
|
#
|
|
|
|
# form_for @user do |f|
|
|
|
|
# f.button :submit
|
|
|
|
# end
|
|
|
|
#
|
2012-02-13 21:50:39 -05:00
|
|
|
# It just acts as a proxy to method name given. We also alias original Rails
|
|
|
|
# button implementation (3.2 forward (to delegate to the original when
|
|
|
|
# calling `f.button :button`.
|
2009-12-10 15:03:27 -05:00
|
|
|
#
|
2012-12-14 14:36:54 -05:00
|
|
|
alias_method :button_button, :button
|
2010-02-06 15:44:07 -05:00
|
|
|
def button(type, *args, &block)
|
2012-02-20 22:42:06 -05:00
|
|
|
options = args.extract_options!.dup
|
2011-09-11 22:46:53 -04:00
|
|
|
options[:class] = [SimpleForm.button_class, options[:class]].compact
|
2010-08-07 16:18:03 -04:00
|
|
|
args << options
|
2010-11-07 04:18:37 -05:00
|
|
|
if respond_to?("#{type}_button")
|
|
|
|
send("#{type}_button", *args, &block)
|
2010-02-06 15:44:07 -05:00
|
|
|
else
|
|
|
|
send(type, *args, &block)
|
2009-12-10 15:03:27 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2009-12-10 12:57:24 -05:00
|
|
|
# Creates an error tag based on the given attribute, only when the attribute
|
2009-12-10 16:52:23 -05:00
|
|
|
# contains errors. All the given options are sent as :error_html.
|
|
|
|
#
|
|
|
|
# == Examples
|
|
|
|
#
|
|
|
|
# f.error :name
|
2013-01-28 16:02:59 -05:00
|
|
|
# f.error :name, id: "cool_error"
|
2009-12-10 17:11:15 -05:00
|
|
|
#
|
2009-12-13 16:16:00 -05:00
|
|
|
def error(attribute_name, options={})
|
2012-02-20 22:42:06 -05:00
|
|
|
options = options.dup
|
|
|
|
|
2011-06-03 10:16:29 -04:00
|
|
|
options[:error_html] = options.except(:error_tag, :error_prefix, :error_method)
|
2010-10-18 17:14:07 -04:00
|
|
|
column = find_attribute_column(attribute_name)
|
|
|
|
input_type = default_input_type(attribute_name, column, options)
|
2011-09-04 05:31:24 -04:00
|
|
|
wrapper.find(:error).
|
2011-09-02 14:33:03 -04:00
|
|
|
render(SimpleForm::Inputs::Base.new(self, attribute_name, column, input_type, options))
|
2009-12-10 10:49:17 -05:00
|
|
|
end
|
|
|
|
|
2011-04-27 15:58:59 -04:00
|
|
|
# Return the error but also considering its name. This is used
|
|
|
|
# when errors for a hidden field need to be shown.
|
|
|
|
#
|
|
|
|
# == Examples
|
|
|
|
#
|
|
|
|
# f.full_error :token #=> <span class="error">Token is invalid</span>
|
|
|
|
#
|
|
|
|
def full_error(attribute_name, options={})
|
2012-02-20 22:42:06 -05:00
|
|
|
options = options.dup
|
|
|
|
|
2011-04-27 16:07:47 -04:00
|
|
|
options[:error_prefix] ||= if object.class.respond_to?(:human_attribute_name)
|
2011-04-27 15:58:59 -04:00
|
|
|
object.class.human_attribute_name(attribute_name.to_s)
|
|
|
|
else
|
|
|
|
attribute_name.to_s.humanize
|
|
|
|
end
|
|
|
|
|
|
|
|
error(attribute_name, options)
|
|
|
|
end
|
|
|
|
|
2009-12-10 16:52:23 -05: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
|
2013-01-28 16:02:59 -05:00
|
|
|
# f.hint :name, id: "cool_hint"
|
2009-12-10 16:52:23 -05:00
|
|
|
# f.hint "Don't forget to accept this"
|
|
|
|
#
|
2009-12-13 16:16:00 -05:00
|
|
|
def hint(attribute_name, options={})
|
2012-02-20 22:42:06 -05:00
|
|
|
options = options.dup
|
|
|
|
|
2011-09-03 12:36:02 -04:00
|
|
|
options[:hint_html] = options.except(:hint_tag, :hint)
|
2010-10-18 17:14:07 -04:00
|
|
|
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
|
2011-09-02 14:45:17 -04:00
|
|
|
|
2011-09-04 05:31:24 -04:00
|
|
|
wrapper.find(:hint).
|
2011-09-02 14:45:17 -04:00
|
|
|
render(SimpleForm::Inputs::Base.new(self, attribute_name, column, input_type, options))
|
2009-12-10 10:49:17 -05:00
|
|
|
end
|
|
|
|
|
2009-12-10 12:57:24 -05:00
|
|
|
# Creates a default label tag for the given attribute. You can give a label
|
2009-12-10 16:52:23 -05: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
|
2013-01-28 16:02:59 -05:00
|
|
|
# f.label :name, label: "Name" # Same as above, but adds required tag
|
2009-12-10 16:52:23 -05:00
|
|
|
#
|
2013-01-28 16:02:59 -05:00
|
|
|
# f.label :name, required: false
|
|
|
|
# f.label :name, id: "cool_label"
|
2009-12-10 16:52:23 -05:00
|
|
|
#
|
2009-12-13 16:16:00 -05:00
|
|
|
def label(attribute_name, *args)
|
2011-09-15 12:02:02 -04:00
|
|
|
return super if args.first.is_a?(String) || block_given?
|
2012-02-20 22:42:06 -05:00
|
|
|
|
|
|
|
options = args.extract_options!.dup
|
2012-02-25 06:47:56 -05:00
|
|
|
options[:label_html] = options.except(:label, :required, :as)
|
2012-02-20 22:42:06 -05:00
|
|
|
|
2010-10-18 17:14:07 -04:00
|
|
|
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
|
2009-12-10 10:49:17 -05:00
|
|
|
end
|
|
|
|
|
2010-07-06 16:29:26 -04:00
|
|
|
# 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
|
2013-01-28 16:02:59 -05:00
|
|
|
# f.error_notification message: 'Something went wrong'
|
|
|
|
# f.error_notification id: 'user_error_message', class: 'form_error'
|
2010-07-06 16:29:26 -04:00
|
|
|
#
|
|
|
|
def error_notification(options={})
|
|
|
|
SimpleForm::ErrorNotification.new(self, options).render
|
|
|
|
end
|
|
|
|
|
2013-01-21 16:57:37 -05:00
|
|
|
# Create a collection of radio inputs for the attribute. Basically this
|
|
|
|
# helper will create a radio input associated with a label for each
|
|
|
|
# text/value option in the collection, using value_method and text_method
|
|
|
|
# to convert these text/value. You can give a symbol or a proc to both
|
|
|
|
# value_method and text_method, that will be evaluated for each item in
|
|
|
|
# the collection.
|
|
|
|
#
|
|
|
|
# == Examples
|
|
|
|
#
|
|
|
|
# form_for @user do |f|
|
|
|
|
# f.collection_radio_buttons :options, [[true, 'Yes'] ,[false, 'No']], :first, :last
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# <input id="user_options_true" name="user[options]" type="radio" value="true" />
|
|
|
|
# <label class="collection_radio_buttons" for="user_options_true">Yes</label>
|
|
|
|
# <input id="user_options_false" name="user[options]" type="radio" value="false" />
|
|
|
|
# <label class="collection_radio_buttons" for="user_options_false">No</label>
|
|
|
|
#
|
|
|
|
# It is also possible to give a block that should generate the radio +
|
|
|
|
# label. To wrap the radio with the label, for instance:
|
|
|
|
#
|
|
|
|
# form_for @user do |f|
|
|
|
|
# f.collection_radio_buttons(
|
|
|
|
# :options, [[true, 'Yes'] ,[false, 'No']], :first, :last
|
|
|
|
# ) do |b|
|
|
|
|
# b.label { b.radio_button + b.text }
|
|
|
|
# end
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# == Options
|
|
|
|
#
|
|
|
|
# Collection radio accepts some extra options:
|
|
|
|
#
|
|
|
|
# * checked => the value that should be checked initially.
|
|
|
|
#
|
|
|
|
# * disabled => the value or values that should be disabled. Accepts a single
|
|
|
|
# item or an array of items.
|
|
|
|
#
|
|
|
|
# * collection_wrapper_tag => the tag to wrap the entire collection.
|
|
|
|
#
|
|
|
|
# * collection_wrapper_class => the CSS class to use for collection_wrapper_tag
|
|
|
|
#
|
|
|
|
# * item_wrapper_tag => the tag to wrap each item in the collection.
|
|
|
|
#
|
|
|
|
# * item_wrapper_class => the CSS class to use for item_wrapper_tag
|
|
|
|
#
|
|
|
|
# * a block => to generate the label + radio or any other component.
|
|
|
|
def collection_radio_buttons(method, collection, value_method, text_method, options = {}, html_options = {}, &block)
|
|
|
|
SimpleForm::Tags::CollectionRadioButtons.new(@object_name, method, @template, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options)).render(&block)
|
|
|
|
end
|
|
|
|
|
|
|
|
# Creates a collection of check boxes for each item in the collection,
|
|
|
|
# associated with a clickable label. Use value_method and text_method to
|
|
|
|
# convert items in the collection for use as text/value in check boxes.
|
|
|
|
# You can give a symbol or a proc to both value_method and text_method,
|
|
|
|
# that will be evaluated for each item in the collection.
|
|
|
|
#
|
|
|
|
# == Examples
|
|
|
|
#
|
|
|
|
# form_for @user do |f|
|
|
|
|
# f.collection_check_boxes :options, [[true, 'Yes'] ,[false, 'No']], :first, :last
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# <input name="user[options][]" type="hidden" value="" />
|
|
|
|
# <input id="user_options_true" name="user[options][]" type="checkbox" value="true" />
|
|
|
|
# <label class="collection_check_boxes" for="user_options_true">Yes</label>
|
|
|
|
# <input name="user[options][]" type="hidden" value="" />
|
|
|
|
# <input id="user_options_false" name="user[options][]" type="checkbox" value="false" />
|
|
|
|
# <label class="collection_check_boxes" for="user_options_false">No</label>
|
|
|
|
#
|
|
|
|
# It is also possible to give a block that should generate the check box +
|
|
|
|
# label. To wrap the check box with the label, for instance:
|
|
|
|
#
|
|
|
|
# form_for @user do |f|
|
|
|
|
# f.collection_check_boxes(
|
|
|
|
# :options, [[true, 'Yes'] ,[false, 'No']], :first, :last
|
|
|
|
# ) do |b|
|
|
|
|
# b.label { b.check_box + b.text }
|
|
|
|
# end
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# == Options
|
|
|
|
#
|
|
|
|
# Collection check box accepts some extra options:
|
|
|
|
#
|
|
|
|
# * checked => the value or values that should be checked initially. Accepts
|
|
|
|
# a single item or an array of items. It overrides existing associations.
|
|
|
|
#
|
|
|
|
# * disabled => the value or values that should be disabled. Accepts a single
|
|
|
|
# item or an array of items.
|
|
|
|
#
|
|
|
|
# * collection_wrapper_tag => the tag to wrap the entire collection.
|
|
|
|
#
|
|
|
|
# * collection_wrapper_class => the CSS class to use for collection_wrapper_tag. This option
|
|
|
|
# is ignored if the :collection_wrapper_tag option is blank.
|
|
|
|
#
|
|
|
|
# * item_wrapper_tag => the tag to wrap each item in the collection.
|
|
|
|
#
|
|
|
|
# * item_wrapper_class => the CSS class to use for item_wrapper_tag
|
|
|
|
#
|
|
|
|
# * a block => to generate the label + check box or any other component.
|
|
|
|
def collection_check_boxes(method, collection, value_method, text_method, options = {}, html_options = {}, &block)
|
|
|
|
SimpleForm::Tags::CollectionCheckBoxes.new(@object_name, method, @template, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options)).render(&block)
|
|
|
|
end
|
|
|
|
|
2011-11-08 06:59:34 -05:00
|
|
|
# Extract the model names from the object_name mess, ignoring numeric and
|
|
|
|
# explicit child indexes.
|
|
|
|
#
|
|
|
|
# Example:
|
|
|
|
#
|
|
|
|
# route[blocks_attributes][0][blocks_learning_object_attributes][1][foo_attributes]
|
|
|
|
# ["route", "blocks", "blocks_learning_object", "foo"]
|
|
|
|
#
|
2013-05-03 22:59:22 -04:00
|
|
|
def lookup_model_names #:nodoc:
|
2011-11-08 06:59:34 -05:00
|
|
|
@lookup_model_names ||= begin
|
|
|
|
child_index = options[:child_index]
|
2013-06-17 09:55:40 -04:00
|
|
|
names = object_name.to_s.scan(/(?!\d)\w+/).flatten
|
2011-11-08 06:59:34 -05:00
|
|
|
names.delete(child_index) if child_index
|
|
|
|
names.each { |name| name.gsub!('_attributes', '') }
|
|
|
|
names.freeze
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# The action to be used in lookup.
|
2013-05-03 22:59:22 -04:00
|
|
|
def lookup_action #:nodoc:
|
2011-11-08 06:59:34 -05:00
|
|
|
@lookup_action ||= begin
|
2013-05-01 18:31:53 -04:00
|
|
|
action = template.controller && template.controller.action_name
|
2011-11-08 06:59:34 -05:00
|
|
|
return unless action
|
|
|
|
action = action.to_sym
|
|
|
|
ACTIONS[action] || action
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2012-01-26 14:22:05 -05:00
|
|
|
private
|
2009-12-08 11:49:14 -05:00
|
|
|
|
2011-09-03 03:01:47 -04:00
|
|
|
# Find an input based on the attribute name.
|
|
|
|
def find_input(attribute_name, options={}, &block) #:nodoc:
|
|
|
|
column = find_attribute_column(attribute_name)
|
|
|
|
input_type = default_input_type(attribute_name, column, options)
|
|
|
|
|
|
|
|
if block_given?
|
|
|
|
SimpleForm::Inputs::BlockInput.new(self, attribute_name, column, input_type, options, &block)
|
|
|
|
else
|
|
|
|
find_mapping(input_type).new(self, attribute_name, column, input_type, options)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2009-12-10 12:57:24 -05: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.
|
2010-10-18 17:14:07 -04:00
|
|
|
def default_input_type(attribute_name, column, options) #:nodoc:
|
|
|
|
return options[:as].to_sym if options[:as]
|
2010-11-25 19:35:38 -05:00
|
|
|
return :select if options[:collection]
|
2011-02-03 20:42:17 -05:00
|
|
|
custom_type = find_custom_type(attribute_name.to_s) and return custom_type
|
2011-02-03 11:06:33 -05:00
|
|
|
|
2010-10-18 17:14:07 -04:00
|
|
|
input_type = column.try(:type)
|
2009-12-09 20:57:05 -05:00
|
|
|
case input_type
|
2011-02-07 20:25:09 -05:00
|
|
|
when :timestamp
|
|
|
|
:datetime
|
|
|
|
when :string, nil
|
|
|
|
case attribute_name.to_s
|
|
|
|
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-09 20:57:05 -05:00
|
|
|
else
|
2011-02-07 20:25:09 -05:00
|
|
|
file_method?(attribute_name) ? :file : (input_type || :string)
|
|
|
|
end
|
|
|
|
else
|
|
|
|
input_type
|
2009-12-08 11:49:14 -05:00
|
|
|
end
|
2009-12-09 20:57:05 -05:00
|
|
|
end
|
2009-12-08 14:08:36 -05:00
|
|
|
|
2011-02-07 06:20:47 -05:00
|
|
|
def find_custom_type(attribute_name) #:nodoc:
|
2011-02-03 11:06:33 -05:00
|
|
|
SimpleForm.input_mappings.find { |match, type|
|
2011-02-03 20:42:17 -05:00
|
|
|
attribute_name =~ match
|
|
|
|
}.try(:last) if SimpleForm.input_mappings
|
2011-02-03 11:06:33 -05:00
|
|
|
end
|
|
|
|
|
2010-10-18 17:14:07 -04:00
|
|
|
def file_method?(attribute_name) #:nodoc:
|
|
|
|
file = @object.send(attribute_name) if @object.respond_to?(attribute_name)
|
2011-02-07 06:20:47 -05:00
|
|
|
file && SimpleForm.file_methods.any? { |m| file.respond_to?(m) }
|
2009-12-11 08:12:57 -05:00
|
|
|
end
|
|
|
|
|
2010-10-18 17:14:07 -04:00
|
|
|
def find_attribute_column(attribute_name) #:nodoc:
|
2011-02-03 20:42:17 -05:00
|
|
|
if @object.respond_to?(:column_for_attribute)
|
|
|
|
@object.column_for_attribute(attribute_name)
|
|
|
|
end
|
2009-12-10 08:57:05 -05:00
|
|
|
end
|
|
|
|
|
2009-12-13 16:16:00 -05:00
|
|
|
def find_association_reflection(association) #:nodoc:
|
2011-02-03 20:42:17 -05:00
|
|
|
if @object.class.respond_to?(:reflect_on_association)
|
|
|
|
@object.class.reflect_on_association(association)
|
|
|
|
end
|
2009-12-10 21:18:14 -05:00
|
|
|
end
|
2011-04-27 14:55:03 -04:00
|
|
|
|
|
|
|
# Attempts to find a mapping. It follows the following rules:
|
|
|
|
#
|
|
|
|
# 1) It tries to find a registered mapping, if succeeds:
|
2011-04-27 16:00:00 -04:00
|
|
|
# a) Try to find an alternative with the same name in the Object scope
|
2011-04-27 14:55:03 -04:00
|
|
|
# b) Or use the found mapping
|
|
|
|
# 2) If not, fallbacks to #{input_type}Input
|
2011-05-05 21:35:19 -04:00
|
|
|
# 3) If not, fallbacks to SimpleForm::Inputs::#{input_type}Input
|
2011-04-27 14:55:03 -04:00
|
|
|
def find_mapping(input_type) #:nodoc:
|
|
|
|
discovery_cache[input_type] ||=
|
|
|
|
if mapping = self.class.mappings[input_type]
|
|
|
|
mapping_override(mapping) || mapping
|
|
|
|
else
|
|
|
|
camelized = "#{input_type.to_s.camelize}Input"
|
|
|
|
attempt_mapping(camelized, Object) || attempt_mapping(camelized, self.class) ||
|
|
|
|
raise("No input found for #{input_type}")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2012-11-07 08:44:45 -05:00
|
|
|
def find_wrapper_mapping(input_type) #:nodoc:
|
2012-08-11 08:52:10 -04:00
|
|
|
SimpleForm.wrapper_mappings && SimpleForm.wrapper_mappings[input_type]
|
|
|
|
end
|
|
|
|
|
2011-04-27 14:55:03 -04:00
|
|
|
# If cache_discovery is enabled, use the class level cache that persists
|
|
|
|
# between requests, otherwise use the instance one.
|
|
|
|
def discovery_cache #:nodoc:
|
|
|
|
if SimpleForm.cache_discovery
|
|
|
|
self.class.discovery_cache
|
|
|
|
else
|
|
|
|
@discovery_cache ||= {}
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def mapping_override(klass) #:nodoc:
|
|
|
|
name = klass.name
|
|
|
|
if name =~ /^SimpleForm::Inputs/
|
2011-04-27 15:26:02 -04:00
|
|
|
attempt_mapping name.split("::").last, Object
|
2011-04-27 14:55:03 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def attempt_mapping(mapping, at) #:nodoc:
|
|
|
|
return if SimpleForm.inputs_discovery == false && at == Object
|
|
|
|
|
|
|
|
begin
|
|
|
|
at.const_get(mapping)
|
|
|
|
rescue NameError => e
|
2012-11-07 08:43:02 -05:00
|
|
|
raise if e.message !~ /#{mapping}$/
|
2011-04-27 14:55:03 -04:00
|
|
|
end
|
|
|
|
end
|
2009-11-18 18:50:43 -05:00
|
|
|
end
|
|
|
|
end
|