diff --git a/TODO.rdoc b/TODO.rdoc
index 7f276451..0d856858 100644
--- a/TODO.rdoc
+++ b/TODO.rdoc
@@ -2,7 +2,6 @@
* Sample CSS
* Add default size support
-* Allow block to be given to overwrite content
== Validations
diff --git a/lib/simple_form.rb b/lib/simple_form.rb
index 2909a1c5..cabb4856 100644
--- a/lib/simple_form.rb
+++ b/lib/simple_form.rb
@@ -3,29 +3,29 @@ require 'simple_form/action_view_extensions/builder'
require 'simple_form/action_view_extensions/instance_tag'
module SimpleForm
- autoload :Components, 'simple_form/components'
- autoload :FormBuilder, 'simple_form/form_builder'
- autoload :I18nCache, 'simple_form/i18n_cache'
- autoload :MapType, 'simple_form/map_type'
- autoload :RequiredHelpers, 'simple_form/required_helpers'
+ autoload :Components, 'simple_form/components'
+ autoload :FormBuilder, 'simple_form/form_builder'
+ autoload :I18nCache, 'simple_form/i18n_cache'
+ autoload :Inputs, 'simple_form/inputs'
+ autoload :MapType, 'simple_form/map_type'
- # Default tag used in components.
- mattr_accessor :component_tag
- @@component_tag = :span
+ # Default tag used in hints.
+ mattr_accessor :hint_tag
+ @@hint_tag = :span
+
+ # Default tag used in errors.
+ mattr_accessor :error_tag
+ @@error_tag = :span
# Components used by the form builder.
mattr_accessor :components
- @@components = [
- SimpleForm::Components::Wrapper, SimpleForm::Components::Label,
- SimpleForm::Components::Input, SimpleForm::Components::Hint,
- SimpleForm::Components::Error
- ]
+ @@components = [ :label, :input, :hint, :error ]
- # Series of attemps to detect a default label method for collection
+ # Series of attemps to detect a default label method for collection.
mattr_accessor :collection_label_methods
@@collection_label_methods = [ :to_label, :name, :title, :to_s ]
- # Series of attemps to detect a default value method for collection
+ # Series of attemps to detect a default value method for collection.
mattr_accessor :collection_value_methods
@@collection_value_methods = [ :id, :to_s ]
diff --git a/lib/simple_form/action_view_extensions/builder.rb b/lib/simple_form/action_view_extensions/builder.rb
index 7d12d20a..a656bc2b 100644
--- a/lib/simple_form/action_view_extensions/builder.rb
+++ b/lib/simple_form/action_view_extensions/builder.rb
@@ -68,7 +68,7 @@ module SimpleForm
# * disabled => the value or values that should be disabled. Accepts a single
# item or an array of items.
#
- def collection_check_box(attribute, collection, value_method, text_method, options={}, html_options={})
+ def collection_check_boxes(attribute, collection, value_method, text_method, options={}, html_options={})
collection.inject('') do |result, item|
value = item.send value_method
text = item.send text_method
@@ -77,7 +77,7 @@ module SimpleForm
default_html_options[:multiple] = true
result << check_box(attribute, default_html_options, value, '') <<
- label("#{attribute}_#{value}", text, :class => "collection_check_box")
+ label("#{attribute}_#{value}", text, :class => "collection_check_boxes")
end
end
diff --git a/lib/simple_form/components.rb b/lib/simple_form/components.rb
index 0b002b76..0588aeeb 100644
--- a/lib/simple_form/components.rb
+++ b/lib/simple_form/components.rb
@@ -1,10 +1,8 @@
module SimpleForm
module Components
- autoload :Base, 'simple_form/components/base'
- autoload :Error, 'simple_form/components/error'
- autoload :Hint, 'simple_form/components/hint'
- autoload :Input, 'simple_form/components/input'
- autoload :Label, 'simple_form/components/label'
+ autoload :Errors, 'simple_form/components/errors'
+ autoload :Hints, 'simple_form/components/hints'
+ autoload :Labels, 'simple_form/components/labels'
autoload :Wrapper, 'simple_form/components/wrapper'
end
end
\ No newline at end of file
diff --git a/lib/simple_form/components/base.rb b/lib/simple_form/components/base.rb
deleted file mode 100644
index 2f71901d..00000000
--- a/lib/simple_form/components/base.rb
+++ /dev/null
@@ -1,113 +0,0 @@
-module SimpleForm
- module Components
- # The component is the core of SimpleForm. SimpleForm can be customized simply
- # with the addition of new components to the component stack. A component just
- # need to be initialized with two values, the builder and the next component to
- # be invoked and respond to call.
- #
- # The Base component is a raw component with some helpers and a default behavior
- # of prepending the content available in the method content.
- class Base
- delegate :template, :object, :object_name, :attribute_name, :column,
- :reflection, :input_type, :options, :to => :@builder
-
- # When action is create or update, we still should use new and edit
- ACTIONS = {
- :create => :new,
- :update => :edit
- }
-
- def self.basename
- @basename ||= name.split("::").last.underscore.to_sym
- end
-
- def initialize(builder, component)
- @builder = builder
- @component = component
- end
-
- # Generate component content and call next component in the stack. When a
- # component is invalid it will be skipped.
- def call
- return @component.call unless valid?
- content + @component.call
- end
-
- def valid?
- true
- end
-
- def hidden_input?
- input_type == :hidden
- end
-
- def basename
- self.class.basename
- end
-
- # Find reflection name when available, otherwise use attribute
- def reflection_or_attribute_name
- reflection ? reflection.name : attribute_name
- end
-
- # Default html options for a component. Passed as a parameter for simple
- # form component using component name as follows:
- #
- # label_html => {}
- # input_html => {}
- # hint_html => {}
- # error_html => {}
- # wrapper_html => {}
- #
- def component_html_options
- options[:"#{basename}_html"] || {}
- end
-
- # Renders default content tag for components, using default html class
- # and user defined parameters.
- # Default component tag can be configured in SimpleForm.component_tag.
- def component_tag(content)
- html_options = component_html_options
- html_options[:class] = "#{basename} #{html_options[:class]}".strip
- template.content_tag(SimpleForm.component_tag, content, html_options)
- end
-
- # Lookup translations for components using I18n, based on object name,
- # actual action and attribute name. Lookup priority as follows:
- #
- # simple_form.{type}.{model}.{action}.{attribute}
- # simple_form.{type}.{model}.{attribute}
- # simple_form.{type}.{attribute}
- #
- # Type is used for :labels and :hints.
- # Model is the actual object name, for a @user object you'll have :user.
- # Action is the action being rendered, usually :new or :edit.
- # And attribute is the attribute itself, :name for example.
- # Example:
- #
- # simple_form:
- # labels:
- # user:
- # new:
- # email: 'E-mail para efetuar o sign in.'
- # edit:
- # email: 'E-mail.'
- #
- # Take a look at our locale example file.
- def translate(default='')
- lookups = []
- lookups << :"#{object_name}.#{lookup_action}.#{reflection_or_attribute_name}"
- lookups << :"#{object_name}.#{reflection_or_attribute_name}"
- lookups << :"#{reflection_or_attribute_name}"
- lookups << default
- I18n.t(lookups.shift, :scope => :"simple_form.#{basename.to_s.pluralize}", :default => lookups)
- end
-
- # The action to be used in lookup.
- def lookup_action
- action = template.controller.action_name.to_sym
- ACTIONS[action] || action
- end
- end
- end
-end
diff --git a/lib/simple_form/components/error.rb b/lib/simple_form/components/error.rb
deleted file mode 100644
index 8241fd41..00000000
--- a/lib/simple_form/components/error.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-module SimpleForm
- module Components
- # General error component. Responsible for verifying whether an object
- # exists and there are errors on the attribute being generated. If errors
- # exists than the component will be rendered, otherwise will be skipped.
- class Error < Base
- def valid?
- object && !hidden_input? && !errors.blank?
- end
-
- def errors
- @errors ||= (errors_on_attribute + errors_on_association).compact
- end
-
- def errors_on_attribute
- Array(object.errors[attribute_name])
- end
-
- def errors_on_association
- reflection ? Array(object.errors[reflection.name]) : []
- end
-
- def content
- component_tag errors.to_sentence
- end
- end
- end
-end
diff --git a/lib/simple_form/components/errors.rb b/lib/simple_form/components/errors.rb
new file mode 100644
index 00000000..09c70e44
--- /dev/null
+++ b/lib/simple_form/components/errors.rb
@@ -0,0 +1,35 @@
+module SimpleForm
+ module Components
+ module Errors
+ def error
+ template.content_tag(error_tag, error_text, error_html_options) if object && errors.present?
+ end
+
+ def error_tag
+ options[:error_tag] || SimpleForm.error_tag
+ end
+
+ def error_text
+ errors.to_sentence
+ end
+
+ def error_html_options
+ html_options_for(:error, :error)
+ end
+
+ protected
+
+ def errors
+ @errors ||= (errors_on_attribute + errors_on_association).compact
+ end
+
+ def errors_on_attribute
+ Array(object.errors[attribute_name])
+ end
+
+ def errors_on_association
+ reflection ? Array(object.errors[reflection.name]) : []
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/lib/simple_form/components/hint.rb b/lib/simple_form/components/hint.rb
deleted file mode 100644
index 9de8e1d5..00000000
--- a/lib/simple_form/components/hint.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-module SimpleForm
- module Components
- # Basic hint component, which verifies whether a user has defined a hint
- # either on the input or through i18n lookup. If no hint is found, the
- # component is skipped.
- class Hint < Base
- def valid?
- !hidden_input? && !hint.blank?
- end
-
- def hint
- @hint ||= options[:hint] || translate
- end
-
- def content
- component_tag hint
- end
- end
- end
-end
diff --git a/lib/simple_form/components/hints.rb b/lib/simple_form/components/hints.rb
new file mode 100644
index 00000000..ebcee571
--- /dev/null
+++ b/lib/simple_form/components/hints.rb
@@ -0,0 +1,21 @@
+module SimpleForm
+ module Components
+ module Hints
+ def hint
+ template.content_tag(hint_tag, hint_text, hint_html_options) unless hint_text.blank?
+ end
+
+ def hint_tag
+ options[:hint_tag] || SimpleForm.hint_tag
+ end
+
+ def hint_text
+ @hint_text ||= options[:hint] || translate(:hints)
+ end
+
+ def hint_html_options
+ html_options_for(:hint, :hint)
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/lib/simple_form/components/input.rb b/lib/simple_form/components/input.rb
deleted file mode 100644
index b4912f5b..00000000
--- a/lib/simple_form/components/input.rb
+++ /dev/null
@@ -1,122 +0,0 @@
-module SimpleForm
- module Components
- # Default input component, responsible for mapping column attributes from
- # database to inputs to be rendered.
- class Input < Base
- include RequiredHelpers
- extend I18nCache
- extend MapType
-
- map_type :boolean, :to => :check_box
- map_type :string, :to => :text_field
- map_type :password, :to => :password_field
- map_type :text, :to => :text_area
- map_type :file, :to => :file_field
- map_type :hidden, :to => :hidden_field
-
- # Numeric types
- map_type :integer, :float, :decimal, :to => :text_field
-
- # Date/time types
- map_type :datetime, :to => :datetime_select, :options => true
- map_type :date, :to => :date_select, :options => true
- map_type :time, :to => :time_select, :options => true
-
- # Collection types
- map_type :select, :to => :collection_select, :options => true, :collection => true
- map_type :radio, :to => :collection_radio, :options => true, :collection => true
- map_type :check_boxes, :to => :collection_check_box, :options => true, :collection => true
-
- # With priority zones
- map_type :country, :to => :country_select, :options => true, :with_priority => true
- map_type :time_zone, :to => :time_zone_select, :options => true, :with_priority => true
-
- # Default boolean collection for use with selects/radios when no
- # collection is given. Always fallback to this boolean collection.
- # Texts can be translated using i18n in "simple_form.true" and
- # "simple_form.false" keys. See the example locale file.
- def self.boolean_collection
- i18n_cache :boolean_collection do
- [ [I18n.t(:"simple_form.yes", :default => 'Yes'), true],
- [I18n.t(:"simple_form.no", :default => 'No'), false] ]
- end
- end
-
- # Generate the input through the mapped option. Apply correct behaviors
- # for collections and add options whenever the input requires it.
- def content
- mapping = self.class.mappings[input_type]
- raise "Invalid input type #{input_type.inspect}" unless mapping
-
- args = [ attribute_name ]
- apply_with_priority_behavior(args) if mapping.with_priority
- apply_collection_behavior(args) if mapping.collection
- apply_options_behavior(args) if mapping.options
- apply_html_options(args)
-
- @builder.send(mapping.method, *args)
- end
-
- protected
-
- # Applies priority behavior to configured types.
- def apply_with_priority_behavior(args)
- priorities = options[:priority] || SimpleForm.send(:"#{input_type}_priority")
- args.push(priorities)
- end
-
- # Applies default collection behavior, mapping the default collection to
- # boolean collection if it was not set, and defining default include_blank
- # option
- def apply_collection_behavior(args)
- collection = (options[:collection] || self.class.boolean_collection).to_a
- detect_collection_methods(collection, options)
- args.push(collection, options[:value_method], options[:label_method])
- end
-
- # Apply default behavior for inputs that need extra options, such as date
- # and time selects.
- def apply_options_behavior(args)
- options[:include_blank] = true unless skip_include_blank?
- args << options
- end
-
- # Adds default html options to the input based on db column information.
- def apply_html_options(args)
- html_options = component_html_options
-
- if column && [:string, :password, :decimal, :float].include?(input_type)
- html_options[:maxlength] ||= column.limit
- end
-
- args << html_options
- end
-
- # Check if :include_blank must be included by default.
- def skip_include_blank?
- options.key?(:prompt) || options.key?(:include_blank) || options[:input_html].try(:[], :multiple)
- end
-
- # Detect the right method to find the label and value for a collection.
- # If no label or value method are defined, will attempt to find them based
- # on default label and value methods that can be configured through
- # SimpleForm.collection_label_methods and
- # SimpleForm.collection_value_methods.
- def detect_collection_methods(collection, options)
- sample = collection.first || collection.last
-
- case sample
- when Array
- label, value = :first, :last
- when Integer
- label, value = :to_s, :to_i
- when String, NilClass
- label, value = :to_s, :to_s
- end
-
- options[:label_method] ||= label || SimpleForm.collection_label_methods.find { |m| sample.respond_to?(m) }
- options[:value_method] ||= value || SimpleForm.collection_value_methods.find { |m| sample.respond_to?(m) }
- end
- end
- end
-end
diff --git a/lib/simple_form/components/label.rb b/lib/simple_form/components/label.rb
deleted file mode 100644
index 1a932057..00000000
--- a/lib/simple_form/components/label.rb
+++ /dev/null
@@ -1,89 +0,0 @@
-module SimpleForm
- module Components
- # Responsible for rendering label with default options, such as required
- # and translation using i18n. By default all fields are required, and label
- # will prepend a default string with a required mark in all labels. You can
- # disable the required option just passing :required => false in your
- # label/input declaration. You can also use i18n to change the required
- # text, mark or entire html string that is generated.
- class Label < Base
- include RequiredHelpers
- extend I18nCache
-
- def self.translate_required_html
- i18n_cache :translate_required_html do
- I18n.t(:"simple_form.required.html", :default =>
- %[#{translate_required_mark}]
- )
- end
- end
-
- def self.translate_required_text
- I18n.t(:"simple_form.required.text", :default => 'required')
- end
-
- def self.translate_required_mark
- I18n.t(:"simple_form.required.mark", :default => '*')
- end
-
- def valid?
- !hidden_input?
- end
-
- # Overwrite html for option if the user has changed input id, so the label
- # will always point correctly to the input. Renders a default label.
- def content
- html_options = component_html_options
- html_options[:for] = options[:input_html][:id] if options.key?(:input_html)
- @builder.label(label_target, text, html_options)
- end
-
- # Map attribute to specific name when dealing with date/time/timestamp,
- # ensuring label will always be "clickable". For better accessibility.
- def label_target
- case input_type
- when :date, :datetime
- "#{attribute_name}_1i"
- when :time
- "#{attribute_name}_4i"
- else
- attribute_name
- end
- end
-
- # The method that actually generates the label. This can be overwriten using
- # a SimpleForm configuration value:
- #
- # SimpleForm.label_text = lambda do |label_text, required_text|
- # required_text + label_text + ":"
- # end
- def text
- SimpleForm.label_text.call(label_text, required_text)
- end
-
- # The user is able to pass a label with the :label option, or it will
- # fallback to label lookup.
- def label_text
- options[:label] || translate_label
- end
-
- # Default required text when attribute is required.
- def required_text
- attribute_required? ? self.class.translate_required_html.dup : ''
- end
-
- # Attempts to translate the label using default i18n lookup. If no
- # translation is found, fallback to human_attribute_name if it is
- # available or just use the attribute itself humanized.
- def translate_label
- default = if object.class.respond_to?(:human_attribute_name)
- object.class.human_attribute_name(reflection_or_attribute_name.to_s)
- else
- attribute_name.to_s.humanize
- end
-
- translate(default)
- end
- end
- end
-end
diff --git a/lib/simple_form/components/labels.rb b/lib/simple_form/components/labels.rb
new file mode 100644
index 00000000..26898fdf
--- /dev/null
+++ b/lib/simple_form/components/labels.rb
@@ -0,0 +1,68 @@
+module SimpleForm
+ module Components
+ module Labels
+ def self.included(base)
+ base.extend ClassMethods
+ end
+
+ module ClassMethods #:nodoc:
+ def translate_required_html
+ i18n_cache :translate_required_html do
+ I18n.t(:"simple_form.required.html", :default =>
+ %[#{translate_required_mark}]
+ )
+ end
+ end
+
+ def translate_required_text
+ I18n.t(:"simple_form.required.text", :default => 'required')
+ end
+
+ def translate_required_mark
+ I18n.t(:"simple_form.required.mark", :default => '*')
+ end
+ end
+
+ def label
+ @builder.label(label_target, label_text, label_html_options)
+ end
+
+ def label_text
+ SimpleForm.label_text.call(raw_label_text, required_label_text)
+ end
+
+ def label_target
+ attribute_name
+ end
+
+ def label_html_options
+ label_options = html_options_for(:label, input_type, required_class)
+ label_options[:for] = options[:input_html][:id] if options.key?(:input_html)
+ label_options
+ end
+
+ protected
+
+ def raw_label_text #:nodoc:
+ options[:label] || label_translation
+ end
+
+ # Default required text when attribute is required.
+ def required_label_text #:nodoc:
+ attribute_required? ? self.class.translate_required_html.dup : ''
+ end
+
+ # First check human attribute name and then labels.
+ # TODO Remove me in Rails > 2.3.5
+ def label_translation #:nodoc:
+ default = if object.class.respond_to?(:human_attribute_name)
+ object.class.human_attribute_name(reflection_or_attribute_name.to_s)
+ else
+ attribute_name.to_s.humanize
+ end
+
+ translate(:labels, default)
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/lib/simple_form/components/wrapper.rb b/lib/simple_form/components/wrapper.rb
index 2db6f2ab..3d75f364 100644
--- a/lib/simple_form/components/wrapper.rb
+++ b/lib/simple_form/components/wrapper.rb
@@ -1,19 +1,21 @@
module SimpleForm
module Components
- # Wrapper component. The last the will be executed by default, responsible
- # for wrapping the entire stack in a wrapper tag if it is configured.
- class Wrapper < Base
- include RequiredHelpers
-
- def call
- tag = options[:wrapper] || SimpleForm.wrapper_tag
-
- if tag
- template.content_tag(tag, @component.call, component_html_options)
+ module Wrapper
+ def wrap(content)
+ if wrapper_tag && options[:wrapper] != false
+ template.content_tag(wrapper_tag, content, wrapper_html_options)
else
- @component.call
+ content
end
end
+
+ def wrapper_tag
+ options[:wrapper_tag] || SimpleForm.wrapper_tag
+ end
+
+ def wrapper_html_options
+ html_options_for(:wrapper, input_type, required_class)
+ end
end
end
-end
+end
\ No newline at end of file
diff --git a/lib/simple_form/form_builder.rb b/lib/simple_form/form_builder.rb
index a1dc0ed2..9e7b27ce 100644
--- a/lib/simple_form/form_builder.rb
+++ b/lib/simple_form/form_builder.rb
@@ -3,7 +3,14 @@ module SimpleForm
attr_reader :template, :object_name, :object, :attribute_name, :column,
:reflection, :input_type, :options
- TERMINATOR = lambda { "" }
+ extend MapType
+ include SimpleForm::Inputs
+
+ map_type :boolean, :password, :text, :file, :to => SimpleForm::Inputs::MappingInput
+ map_type :string, :integer, :decimal, :float, :to => SimpleForm::Inputs::TextFieldInput
+ 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
# Basic input helper, combines all components in the stack to generate
# input html based on options the user define and some guesses through
@@ -70,15 +77,16 @@ module SimpleForm
# 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.
#
- def input(attribute_name, options={})
+ def input(attribute_name, options={}, &block)
define_simple_form_attributes(attribute_name, options)
- component = TERMINATOR
- SimpleForm.components.reverse.each do |klass|
- next if @options[klass.basename] == false
- component = klass.new(self, component)
+ if block_given?
+ SimpleForm::Inputs::BlockInput.new(self, block).render
+ else
+ klass = self.class.mappings[input_type] ||
+ self.class.const_get(:"#{input_type.to_s.camelize}Input")
+ klass.new(self).render
end
- component.call
end
alias :attribute :input
@@ -117,7 +125,22 @@ module SimpleForm
# f.association :company, :scope => [ :public, :not_broken ]
# # Same as doing Company.public.not_broken.all
#
- def association(association, options={})
+ # == 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?
+
raise ArgumentError, "Association cannot be used in forms not associated with an object" unless @object
options[:as] ||= :select
@@ -210,7 +233,7 @@ module SimpleForm
#
def error(attribute_name, options={})
define_simple_form_attributes(attribute_name, :error_html => options)
- SimpleForm::Components::Error.new(self, TERMINATOR).call
+ SimpleForm::Inputs::Base.new(self).error
end
# Creates a hint tag for the given attribute. Accepts a symbol indicating
@@ -226,7 +249,7 @@ module SimpleForm
def hint(attribute_name, options={})
attribute_name, options[:hint] = nil, attribute_name if attribute_name.is_a?(String)
define_simple_form_attributes(attribute_name, :hint => options.delete(:hint), :hint_html => options)
- SimpleForm::Components::Hint.new(self, TERMINATOR).call
+ SimpleForm::Inputs::Base.new(self).hint
end
# Creates a default label tag for the given attribute. You can give a label
@@ -247,7 +270,7 @@ module SimpleForm
options = args.extract_options!
define_simple_form_attributes(attribute_name, :label => options.delete(:label),
:label_html => options, :required => options.delete(:required))
- SimpleForm::Components::Label.new(self, TERMINATOR).call
+ SimpleForm::Inputs::Base.new(self).label
end
private
diff --git a/lib/simple_form/i18n_cache.rb b/lib/simple_form/i18n_cache.rb
index fc8e567d..74f056be 100644
--- a/lib/simple_form/i18n_cache.rb
+++ b/lib/simple_form/i18n_cache.rb
@@ -8,11 +8,15 @@ module SimpleForm
end
def get_i18n_cache(key)
- instance_variable_get(:"@#{key}") || reset_i18n_cache(key)
+ if class_variable_defined?(:"@@#{key}")
+ class_variable_get(:"@@#{key}")
+ else
+ reset_i18n_cache(key)
+ end
end
def reset_i18n_cache(key)
- instance_variable_set(:"@#{key}", {})
+ class_variable_set(:"@@#{key}", {})
end
end
end
diff --git a/lib/simple_form/inputs.rb b/lib/simple_form/inputs.rb
new file mode 100644
index 00000000..1f422f54
--- /dev/null
+++ b/lib/simple_form/inputs.rb
@@ -0,0 +1,12 @@
+module SimpleForm
+ module Inputs
+ autoload :Base, 'simple_form/inputs/base'
+ autoload :BlockInput, 'simple_form/inputs/block_input'
+ autoload :CollectionInput, 'simple_form/inputs/collection_input'
+ autoload :DateTimeInput, 'simple_form/inputs/date_time_input'
+ autoload :HiddenInput, 'simple_form/inputs/hidden_input'
+ autoload :MappingInput, 'simple_form/inputs/mapping_input'
+ autoload :PriorityInput, 'simple_form/inputs/priority_input'
+ autoload :TextFieldInput, 'simple_form/inputs/text_field_input'
+ end
+end
\ No newline at end of file
diff --git a/lib/simple_form/inputs/base.rb b/lib/simple_form/inputs/base.rb
new file mode 100644
index 00000000..adc4773b
--- /dev/null
+++ b/lib/simple_form/inputs/base.rb
@@ -0,0 +1,113 @@
+module SimpleForm
+ module Inputs
+ class Base
+ extend I18nCache
+
+ # When action is create or update, we still should use new and edit
+ ACTIONS = {
+ :create => :new,
+ :update => :edit
+ }
+
+ include SimpleForm::Components::Errors
+ include SimpleForm::Components::Hints
+ include SimpleForm::Components::Labels
+ include SimpleForm::Components::Wrapper
+
+ delegate :template, :object, :object_name, :attribute_name, :column,
+ :reflection, :input_type, :options, :to => :@builder
+
+ def initialize(builder)
+ @builder = builder
+ end
+
+ def input
+ raise NotImplementedError
+ end
+
+ def input_options
+ options[:include_blank] = true unless skip_include_blank?
+ options
+ end
+
+ def input_html_options
+ html_options_for(:input, input_type, required_class)
+ end
+
+ def render
+ content = SimpleForm.components.map do |component|
+ next if options[component] == false
+ send(component)
+ end
+ content.compact!
+ wrap(content.join).html_safe!
+ end
+
+ protected
+
+ def attribute_required?
+ options[:required] != false
+ end
+
+ def required_class
+ attribute_required? ? :required : :optional
+ end
+
+ # Find reflection name when available, otherwise use attribute
+ def reflection_or_attribute_name
+ reflection ? reflection.name : attribute_name
+ end
+
+ # Check if :include_blank must be included by default.
+ def skip_include_blank?
+ options.key?(:prompt) || options.key?(:include_blank)
+ end
+
+ # Retrieve options for the given namespace from the options hash
+ def html_options_for(namespace, *extra)
+ html_options = options[:"#{namespace}_html"] || {}
+ html_options[:class] = (extra << html_options[:class]).join(' ').strip if extra.present?
+ html_options
+ end
+
+ # Lookup translations for the given namespace using I18n, based on object name,
+ # actual action and attribute name. Lookup priority as follows:
+ #
+ # simple_form.{namespace}.{model}.{action}.{attribute}
+ # simple_form.{namespace}.{model}.{attribute}
+ # simple_form.{namespace}.{attribute}
+ #
+ # Namespace is used for :labels and :hints.
+ #
+ # Model is the actual object name, for a @user object you'll have :user.
+ # Action is the action being rendered, usually :new or :edit.
+ # And attribute is the attribute itself, :name for example.
+ #
+ # Example:
+ #
+ # simple_form:
+ # labels:
+ # user:
+ # new:
+ # email: 'E-mail para efetuar o sign in.'
+ # edit:
+ # email: 'E-mail.'
+ #
+ # Take a look at our locale example file.
+ def translate(namespace, default='')
+ lookups = []
+ lookups << :"#{object_name}.#{lookup_action}.#{reflection_or_attribute_name}"
+ lookups << :"#{object_name}.#{reflection_or_attribute_name}"
+ lookups << :"#{reflection_or_attribute_name}"
+ lookups << default
+ I18n.t(lookups.shift, :scope => :"simple_form.#{namespace}", :default => lookups)
+ end
+
+ # The action to be used in lookup.
+ def lookup_action
+ action = template.controller.action_name.to_sym
+ ACTIONS[action] || action
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/lib/simple_form/inputs/block_input.rb b/lib/simple_form/inputs/block_input.rb
new file mode 100644
index 00000000..d75b1cfe
--- /dev/null
+++ b/lib/simple_form/inputs/block_input.rb
@@ -0,0 +1,13 @@
+module SimpleForm
+ module Inputs
+ class BlockInput < Base
+ def initialize(builder, block)
+ @builder, @block = builder, block
+ end
+
+ def input
+ template.capture(&@block)
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/lib/simple_form/inputs/collection_input.rb b/lib/simple_form/inputs/collection_input.rb
new file mode 100644
index 00000000..526bfd12
--- /dev/null
+++ b/lib/simple_form/inputs/collection_input.rb
@@ -0,0 +1,50 @@
+module SimpleForm
+ module Inputs
+ class CollectionInput < Base
+ # Default boolean collection for use with selects/radios when no
+ # collection is given. Always fallback to this boolean collection.
+ # Texts can be translated using i18n in "simple_form.yes" and
+ # "simple_form.no" keys. See the example locale file.
+ def self.boolean_collection
+ i18n_cache :boolean_collection do
+ [ [I18n.t(:"simple_form.yes", :default => 'Yes'), true],
+ [I18n.t(:"simple_form.no", :default => 'No'), false] ]
+ end
+ end
+
+ def input
+ collection = (options[:collection] || self.class.boolean_collection).to_a
+ detect_collection_methods(collection, options)
+ @builder.send(:"collection_#{input_type}", attribute_name, collection, options[:value_method],
+ options[:label_method], input_options, input_html_options)
+ end
+
+ protected
+
+ def skip_include_blank?
+ super || options[:input_html].try(:[], :multiple)
+ end
+
+ # Detect the right method to find the label and value for a collection.
+ # If no label or value method are defined, will attempt to find them based
+ # on default label and value methods that can be configured through
+ # SimpleForm.collection_label_methods and
+ # SimpleForm.collection_value_methods.
+ def detect_collection_methods(collection, options)
+ sample = collection.first || collection.last
+
+ case sample
+ when Array
+ label, value = :first, :last
+ when Integer
+ label, value = :to_s, :to_i
+ when String, NilClass
+ label, value = :to_s, :to_s
+ end
+
+ options[:label_method] ||= label || SimpleForm.collection_label_methods.find { |m| sample.respond_to?(m) }
+ options[:value_method] ||= value || SimpleForm.collection_value_methods.find { |m| sample.respond_to?(m) }
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/lib/simple_form/inputs/date_time_input.rb b/lib/simple_form/inputs/date_time_input.rb
new file mode 100644
index 00000000..d23c3cba
--- /dev/null
+++ b/lib/simple_form/inputs/date_time_input.rb
@@ -0,0 +1,18 @@
+module SimpleForm
+ module Inputs
+ class DateTimeInput < Base
+ def input
+ @builder.send(:"#{input_type}_select", attribute_name, input_options, input_html_options)
+ end
+
+ def label_target
+ case input_type
+ when :date, :datetime
+ "#{attribute_name}_1i"
+ when :time
+ "#{attribute_name}_4i"
+ end
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/lib/simple_form/inputs/hidden_input.rb b/lib/simple_form/inputs/hidden_input.rb
new file mode 100644
index 00000000..dff79cbb
--- /dev/null
+++ b/lib/simple_form/inputs/hidden_input.rb
@@ -0,0 +1,11 @@
+module SimpleForm
+ module Inputs
+ # Handles hidden input.
+ class HiddenInput < Base
+ def render
+ @builder.hidden_field(attribute_name, input_html_options)
+ end
+ alias :input :render
+ end
+ end
+end
\ No newline at end of file
diff --git a/lib/simple_form/inputs/mapping_input.rb b/lib/simple_form/inputs/mapping_input.rb
new file mode 100644
index 00000000..dac7d186
--- /dev/null
+++ b/lib/simple_form/inputs/mapping_input.rb
@@ -0,0 +1,23 @@
+module SimpleForm
+ module Inputs
+ # Uses MapType to handle basic input types.
+ class MappingInput < Base
+ extend MapType
+
+ map_type :boolean, :to => :check_box
+ map_type :password, :to => :password_field
+ map_type :text, :to => :text_area
+ map_type :file, :to => :file_field
+
+ def input
+ @builder.send(input_method, attribute_name, input_html_options)
+ end
+
+ def input_method
+ method = self.class.mappings[input_type]
+ raise "Could not find method for #{input_type.inspect}" unless method
+ method
+ end
+ end
+ end
+end
diff --git a/lib/simple_form/inputs/priority_input.rb b/lib/simple_form/inputs/priority_input.rb
new file mode 100644
index 00000000..6af6798e
--- /dev/null
+++ b/lib/simple_form/inputs/priority_input.rb
@@ -0,0 +1,14 @@
+module SimpleForm
+ module Inputs
+ class PriorityInput < Base
+ def input
+ @builder.send(:"#{input_type}_select", attribute_name, input_priority,
+ input_options, input_html_options)
+ end
+
+ def input_priority
+ options[:priority] || SimpleForm.send(:"#{input_type}_priority")
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/lib/simple_form/inputs/text_field_input.rb b/lib/simple_form/inputs/text_field_input.rb
new file mode 100644
index 00000000..bc8aeb38
--- /dev/null
+++ b/lib/simple_form/inputs/text_field_input.rb
@@ -0,0 +1,16 @@
+module SimpleForm
+ module Inputs
+ # Handles common text field inputs, as String, Numeric, Float and Decimal.
+ class TextFieldInput < Base
+ def input
+ @builder.text_field(attribute_name, input_html_options)
+ end
+
+ def input_html_options
+ input_options = super
+ input_options[:maxlength] ||= column.limit if column
+ input_options
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/lib/simple_form/map_type.rb b/lib/simple_form/map_type.rb
index 6cfa7843..3cb8d3e1 100644
--- a/lib/simple_form/map_type.rb
+++ b/lib/simple_form/map_type.rb
@@ -1,6 +1,4 @@
module SimpleForm
- Mapping = Struct.new(:method, :collection, :options, :with_priority)
-
module MapType
def mappings
@mappings ||= {}
@@ -9,9 +7,7 @@ module SimpleForm
def map_type(*types)
options = types.extract_options!
raise ArgumentError, "You need to give :to as option to map_type" unless options[:to]
- mapping = Mapping.new(options[:to], options[:collection] || false,
- options[:options] || false, options[:with_priority] || false)
- types.each { |t| mappings[t] = mapping }
+ types.each { |t| mappings[t] = options[:to] }
end
end
end
\ No newline at end of file
diff --git a/lib/simple_form/required_helpers.rb b/lib/simple_form/required_helpers.rb
deleted file mode 100644
index c782fa21..00000000
--- a/lib/simple_form/required_helpers.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-module SimpleForm
- module RequiredHelpers
- # Attribute is always required, unless the user has defined the opposite.
- def attribute_required?
- options[:required] != false
- end
-
- def required_class
- attribute_required? ? :required : :optional
- end
-
- # Creates default required classes for attributes, such as .string and
- # .decimal, based on input type, and required class
- def default_css_classes(merge_class=nil)
- "#{input_type} #{required_class} #{merge_class}".strip
- end
-
- # When components may be required, default component html options always
- # must include default css classes.
- def component_html_options
- html_options = super
- html_options[:class] = default_css_classes(html_options[:class])
- html_options
- end
- end
-end
diff --git a/test/action_view_extensions/builder_test.rb b/test/action_view_extensions/builder_test.rb
index 0e228b2b..a45b9b20 100644
--- a/test/action_view_extensions/builder_test.rb
+++ b/test/action_view_extensions/builder_test.rb
@@ -63,7 +63,7 @@ class BuilderTest < ActionView::TestCase
test 'collection check box accepts a collection and generate a serie of checkboxes for value method' do
collection = [Tag.new(1, 'Tag 1'), Tag.new(2, 'Tag 2')]
form_for @user do |f|
- concat f.collection_check_box :tag_ids, collection, :id, :name
+ concat f.collection_check_boxes :tag_ids, collection, :id, :name
end
assert_select "form input[type=hidden][name='user[tag_ids][]'][value=]"
@@ -74,17 +74,17 @@ class BuilderTest < ActionView::TestCase
test 'collection check box accepts a collection and generate a serie of checkboxes with labels for label method' do
collection = [Tag.new(1, 'Tag 1'), Tag.new(2, 'Tag 2')]
form_for @user do |f|
- concat f.collection_check_box :tag_ids, collection, :id, :name
+ concat f.collection_check_boxes :tag_ids, collection, :id, :name
end
- assert_select 'form label.collection_check_box[for=user_tag_ids_1]', 'Tag 1'
- assert_select 'form label.collection_check_box[for=user_tag_ids_2]', 'Tag 2'
+ assert_select 'form label.collection_check_boxes[for=user_tag_ids_1]', 'Tag 1'
+ assert_select 'form label.collection_check_boxes[for=user_tag_ids_2]', 'Tag 2'
end
test 'collection check box accepts selected values as :checked option' do
collection = (1..3).map{|i| [i, "Tag #{i}"] }
form_for @user do |f|
- concat f.collection_check_box :tag_ids, collection, :first, :last, :checked => [1, 3]
+ concat f.collection_check_boxes :tag_ids, collection, :first, :last, :checked => [1, 3]
end
assert_select 'form input[type=checkbox][value=1][checked=checked]'
@@ -95,7 +95,7 @@ class BuilderTest < ActionView::TestCase
test 'collection check box accepts a single checked value' do
collection = (1..3).map{|i| [i, "Tag #{i}"] }
form_for @user do |f|
- concat f.collection_check_box :tag_ids, collection, :first, :last, :checked => 3
+ concat f.collection_check_boxes :tag_ids, collection, :first, :last, :checked => 3
end
assert_select 'form input[type=checkbox][value=3][checked=checked]'
@@ -106,7 +106,7 @@ class BuilderTest < ActionView::TestCase
test 'collection check box accepts multiple disabled items' do
collection = (1..3).map{|i| [i, "Tag #{i}"] }
form_for @user do |f|
- concat f.collection_check_box :tag_ids, collection, :first, :last, :disabled => [1, 3]
+ concat f.collection_check_boxes :tag_ids, collection, :first, :last, :disabled => [1, 3]
end
assert_select 'form input[type=checkbox][value=1][disabled=disabled]'
@@ -117,7 +117,7 @@ class BuilderTest < ActionView::TestCase
test 'collection check box accepts single disable item' do
collection = (1..3).map{|i| [i, "Tag #{i}"] }
form_for @user do |f|
- concat f.collection_check_box :tag_ids, collection, :first, :last, :disabled => 1
+ concat f.collection_check_boxes :tag_ids, collection, :first, :last, :disabled => 1
end
assert_select 'form input[type=checkbox][value=1][disabled=disabled]'
@@ -128,7 +128,7 @@ class BuilderTest < ActionView::TestCase
test 'collection check box accepts a proc to disabled items' do
collection = (1..3).map{|i| [i, "Tag #{i}"] }
form_for @user do |f|
- concat f.collection_check_box :tag_ids, collection, :first, :last, :disabled => proc { |i| i.first == 1 }
+ concat f.collection_check_boxes :tag_ids, collection, :first, :last, :disabled => proc { |i| i.first == 1 }
end
assert_select 'form input[type=checkbox][value=1][disabled=disabled]'
@@ -139,7 +139,7 @@ class BuilderTest < ActionView::TestCase
test 'collection check box accepts html options' do
collection = [[1, 'Tag 1'], [2, 'Tag 2']]
form_for @user do |f|
- concat f.collection_check_box :tag_ids, collection, :first, :last, {}, :class => 'check'
+ concat f.collection_check_boxes :tag_ids, collection, :first, :last, {}, :class => 'check'
end
assert_select 'form input.check[type=checkbox][value=1]'
@@ -150,15 +150,15 @@ class BuilderTest < ActionView::TestCase
collection = [Tag.new(1, 'Tag 1'), Tag.new(2, 'Tag 2')]
form_for @user do |f|
f.fields_for :post do |p|
- concat p.collection_check_box :tag_ids, collection, :id, :name
+ concat p.collection_check_boxes :tag_ids, collection, :id, :name
end
end
assert_select 'form input#user_post_tag_ids_1[type=checkbox][value=1]'
assert_select 'form input#user_post_tag_ids_2[type=checkbox][value=2]'
- assert_select 'form label.collection_check_box[for=user_post_tag_ids_1]', 'Tag 1'
- assert_select 'form label.collection_check_box[for=user_post_tag_ids_2]', 'Tag 2'
+ assert_select 'form label.collection_check_boxes[for=user_post_tag_ids_1]', 'Tag 1'
+ assert_select 'form label.collection_check_boxes[for=user_post_tag_ids_2]', 'Tag 2'
end
# SIMPLE FIELDS
diff --git a/test/components/error_test.rb b/test/components/error_test.rb
index f6bf98d7..941563f1 100644
--- a/test/components/error_test.rb
+++ b/test/components/error_test.rb
@@ -9,28 +9,18 @@ class ErrorTest < ActionView::TestCase
f.input_type = type
f.options = options
- error = SimpleForm::Components::Error.new(f, SimpleForm::FormBuilder::TERMINATOR)
- concat(error.call)
- yield error if block_given?
- end
- end
-
- test 'error should not generate content for hidden fields' do
- with_error_for @user, :name, :hidden do |error|
- assert error.call.blank?
+ concat(SimpleForm::Inputs::Base.new(f).error.to_s)
end
end
test 'error should not generate content for attribute without errors' do
- with_error_for @user, :active, :boolean do |error|
- assert error.call.blank?
- end
+ with_error_for @user, :active, :boolean
+ assert_no_select 'span.error'
end
test 'error should not generate messages when object is not present' do
- with_error_for :project, :name, :string do |error|
- assert error.call.blank?
- end
+ with_error_for :project, :name, :string
+ assert_no_select 'span.error'
end
test 'error should generate messages for attribute with single error' do
diff --git a/test/components/hint_test.rb b/test/components/hint_test.rb
index 6f2c713b..d8e97ca3 100644
--- a/test/components/hint_test.rb
+++ b/test/components/hint_test.rb
@@ -9,22 +9,13 @@ class HintTest < ActionView::TestCase
f.input_type = type
f.options = options
- hint = SimpleForm::Components::Hint.new(f, SimpleForm::FormBuilder::TERMINATOR)
- concat(hint.call)
- yield hint if block_given?
+ concat(SimpleForm::Inputs::Base.new(f).hint.to_s)
end
end
test 'hint should not be generated by default' do
- with_hint_for @user, :name, :string do |hint|
- assert hint.call.blank?
- end
- end
-
- test 'hint should not be generated for hidden fields' do
- with_hint_for @user, :name, :hidden, :hint => 'Use with care...' do |hint|
- assert hint.call.blank?
- end
+ with_hint_for @user, :name, :string
+ assert_no_select 'span.hint'
end
test 'hint should be generated with input text' do
@@ -33,7 +24,7 @@ class HintTest < ActionView::TestCase
end
test 'hint uses the current component tag set' do
- swap SimpleForm, :component_tag => :p do
+ swap SimpleForm, :hint_tag => :p do
with_hint_for @user, :name, :string, :hint => 'Use with care...'
assert_select 'p.hint', 'Use with care...'
end
diff --git a/test/components/label_test.rb b/test/components/label_test.rb
index 2ade6688..fca05f43 100644
--- a/test/components/label_test.rb
+++ b/test/components/label_test.rb
@@ -3,7 +3,7 @@ require 'test_helper'
class LabelTest < ActionView::TestCase
setup do
- SimpleForm::Components::Label.reset_i18n_cache :translate_required_html
+ SimpleForm::Inputs::Base.reset_i18n_cache :translate_required_html
end
def with_label_for(object, attribute_name, type, options={})
@@ -13,15 +13,7 @@ class LabelTest < ActionView::TestCase
f.input_type = type
f.options = options
- label = SimpleForm::Components::Label.new(f, SimpleForm::FormBuilder::TERMINATOR)
- concat(label.call)
- yield label if block_given?
- end
- end
-
- test 'label should not be generated for hidden inputs' do
- with_label_for @user, :name, :hidden do |label|
- assert label.call.blank?
+ concat(SimpleForm::Inputs::Base.new(f).label)
end
end
@@ -175,19 +167,4 @@ class LabelTest < ActionView::TestCase
with_label_for :project, :description, :string, :required => false
assert_no_select 'label.required[for=project_description]'
end
-
- test 'label should point to first option when date input type' do
- with_label_for :project, :created_at, :date
- assert_select 'label[for=project_created_at_1i]'
- end
-
- test 'label should point to first option when datetime input type' do
- with_label_for :project, :created_at, :datetime
- assert_select 'label[for=project_created_at_1i]'
- end
-
- test 'label should point to first option when time input type' do
- with_label_for :project, :created_at, :time
- assert_select 'label[for=project_created_at_4i]'
- end
end
diff --git a/test/form_builder_test.rb b/test/form_builder_test.rb
index 57d82b15..45fd63ff 100644
--- a/test/form_builder_test.rb
+++ b/test/form_builder_test.rb
@@ -2,42 +2,65 @@ require 'test_helper'
class FormBuilderTest < ActionView::TestCase
- def with_form_for(object, attribute, options={})
+ def with_form_for(object, *args, &block)
simple_form_for object do |f|
- concat f.input attribute, options
+ concat f.input(*args, &block)
end
end
def with_button_for(object, *args)
simple_form_for object do |f|
- concat f.button *args
+ concat f.button(*args)
end
end
def with_error_for(object, *args)
simple_form_for object do |f|
- concat f.error *args
+ concat f.error(*args)
end
end
def with_hint_for(object, *args)
simple_form_for object do |f|
- concat f.hint *args
+ concat f.hint(*args)
end
end
def with_label_for(object, *args)
simple_form_for object do |f|
- concat f.label *args
+ concat f.label(*args)
end
end
def with_association_for(object, *args)
simple_form_for object do |f|
- concat f.association *args
+ concat f.association(*args)
end
end
+ # All
+ test 'nested simple fields should yields an instance of FormBuilder' do
+ simple_form_for :user do |f|
+ f.simple_fields_for :posts do |posts_form|
+ assert posts_form.instance_of?(SimpleForm::FormBuilder)
+ end
+ end
+ end
+
+ test 'builder input is html safe' do
+ simple_form_for @user do |f|
+ assert f.input(:name).html_safe?
+ end
+ end
+
+ test 'builder input should allow a block to configure input' do
+ with_form_for @user, :name do
+ concat text_field_tag :foo, :bar, :id => :cool
+ end
+ assert_no_select 'input.string'
+ assert_select 'input#cool'
+ end
+
# INPUT TYPES
test 'builder should generate text fields for string columns' do
with_form_for @user, :name
@@ -136,7 +159,7 @@ class FormBuilderTest < ActionView::TestCase
test 'builder should generate a input with label' do
with_form_for @user, :name
- assert_select 'form label.string[for=user_name]'
+ assert_select 'form label.string[for=user_name]', /Name/
end
test 'builder should be able to disable the label for a input' do
@@ -146,7 +169,12 @@ class FormBuilderTest < ActionView::TestCase
test 'builder should use custom label' do
with_form_for @user, :name, :label => 'Yay!'
- assert_no_select 'form label', 'Yay!'
+ assert_select 'form label', /Yay!/
+ end
+
+ test 'builder should pass options to label' do
+ with_form_for @user, :name, :label_html => { :id => "cool" }
+ assert_select 'form label#cool', /Name/
end
test 'builder should not generate hints for a input' do
@@ -166,6 +194,11 @@ class FormBuilderTest < ActionView::TestCase
end
end
+ test 'builder should pass options to hint' do
+ with_form_for @user, :name, :hint => 'test', :hint_html => { :id => "cool" }
+ assert_select 'span.hint#cool', 'test'
+ end
+
test 'builder should generate errors for attribute without errors' do
with_form_for @user, :credit_limit
assert_no_select 'span.errors'
@@ -181,6 +214,22 @@ class FormBuilderTest < ActionView::TestCase
assert_no_select 'span.error'
end
+ test 'builder should pass options to errors' do
+ with_form_for @user, :name, :error_html => { :id => "cool" }
+ assert_select 'span.error#cool', "can't be blank"
+ end
+
+ test 'builder input should be required by default' do
+ with_form_for @user, :name
+ assert_select 'input.required#user_name'
+ end
+
+ test 'builder input should allow disabling required' do
+ with_form_for @user, :name, :required => false
+ assert_no_select 'input.required'
+ assert_select 'input.optional#user_name'
+ end
+
# WRAPPERS
test 'builder support wrapping around an specific tag' do
swap SimpleForm, :wrapper_tag => :p do
@@ -209,19 +258,11 @@ class FormBuilderTest < ActionView::TestCase
test 'builder allows wrapper tag to be given on demand' do
simple_form_for @user do |f|
- concat f.input :name, :wrapper => :b
+ concat f.input :name, :wrapper_tag => :b
end
assert_select 'form b.required.string'
end
- test 'nested simple fields should yields an instance of FormBuilder' do
- simple_form_for :user do |f|
- f.simple_fields_for :posts do |posts_form|
- assert posts_form.instance_of?(SimpleForm::FormBuilder)
- end
- end
- end
-
# WITHOUT OBJECT
test 'builder should generate properly when object is not present' do
with_form_for :project, :name
@@ -248,7 +289,7 @@ class FormBuilderTest < ActionView::TestCase
end
# ERRORS
- test 'builder should generate an error component tag for the attribute' do
+ test 'builder should generate an error tag for the attribute' do
with_error_for @user, :name
assert_select 'span.error', "can't be blank"
end
@@ -259,7 +300,7 @@ class FormBuilderTest < ActionView::TestCase
end
# HINTS
- test 'builder should generate a hint component tag for the attribute' do
+ test 'builder should generate a hint tag for the attribute' do
store_translations(:en, :simple_form => { :hints => { :user => { :name => "Add your name" }}}) do
with_hint_for @user, :name
assert_select 'span.hint', 'Add your name'
@@ -277,7 +318,7 @@ class FormBuilderTest < ActionView::TestCase
end
# LABELS
- test 'builder should generate a label component tag for the attribute' do
+ test 'builder should generate a label for the attribute' do
with_label_for @user, :name
assert_select 'label.string[for=user_name]', /Name/
end
@@ -369,6 +410,25 @@ class FormBuilderTest < ActionView::TestCase
end
end
+ test 'builder association with a block call simple_fields_for' do
+ simple_form_for @user do |f|
+ f.association :posts do |posts_form|
+ assert posts_form.instance_of?(SimpleForm::FormBuilder)
+ end
+ end
+ end
+
+ test 'builder association forwards collection to simple_fields_for' do
+ calls = 0
+ simple_form_for @user do |f|
+ f.association :company, :collection => Company.all do |c|
+ calls += 1
+ end
+ end
+
+ assert_equal calls, 3
+ end
+
# ASSOCIATIONS - BELONGS TO
test 'builder creates a select for belongs_to associations' do
with_association_for @user, :company
diff --git a/test/components/input_test.rb b/test/inputs_test.rb
similarity index 89%
rename from test/components/input_test.rb
rename to test/inputs_test.rb
index 6e141f78..ddfababe 100644
--- a/test/components/input_test.rb
+++ b/test/inputs_test.rb
@@ -3,27 +3,16 @@ require 'test_helper'
class InputTest < ActionView::TestCase
setup do
- SimpleForm::Components::Input.reset_i18n_cache :boolean_collection
+ SimpleForm::Inputs::CollectionInput.reset_i18n_cache :boolean_collection
end
-
+
def with_input_for(object, attribute_name, type, options={})
simple_form_for object do |f|
- f.attribute_name = attribute_name
- f.column = object.column_for_attribute(attribute_name) if object.respond_to?(:column_for_attribute)
- f.input_type = type
- f.options = options
-
- input = SimpleForm::Components::Input.new(f, SimpleForm::FormBuilder::TERMINATOR)
- concat(input.call)
- yield input if block_given?
+ concat f.input(attribute_name, options.merge(:as => type))
end
end
-
- test 'input should map text field to string attribute' do
- with_input_for @user, :name, :string
- assert_select 'input[name=\'user[name]\'][id=user_name][value=New in Simple Form!]'
- end
-
+
+ # ALL
test 'input should generate css class based on default input type' do
with_input_for @user, :name, :string
assert_select 'input.string'
@@ -37,59 +26,83 @@ class InputTest < ActionView::TestCase
assert_select 'select.datetime'
end
- test 'input should allow passing options to text field' do
- with_input_for @user, :name, :string, :input_html => { :class => 'my_input', :id => 'my_input' }
- assert_select 'input#my_input.my_input'
- end
-
- test 'input should generate a text area for text attributes' do
- with_input_for @user, :description, :text
- assert_select 'textarea.text#user_description'
+ # TextFieldInput
+ test 'input should map text field to string attribute' do
+ with_input_for @user, :name, :string
+ assert_select 'input[name=\'user[name]\'][id=user_name][value=New in Simple Form!]'
end
test 'input should generate an integer text field for integer attributes ' do
with_input_for @user, :age, :integer
assert_select 'input.integer#user_age'
end
-
+
test 'input should generate a float text field for float attributes ' do
with_input_for @user, :age, :float
assert_select 'input.float#user_age'
end
-
+
test 'input should generate a decimal text field for decimal attributes ' do
with_input_for @user, :age, :decimal
assert_select 'input.decimal#user_age'
end
+ test 'input should get options from column definition for string attributes' do
+ with_input_for @user, :name, :string
+ assert_select 'input.string[maxlength=100]'
+ end
+
+ test 'input should get options from column definition for decimal attributes' do
+ with_input_for @user, :credit_limit, :decimal
+ assert_select 'input.decimal[maxlength=15]'
+ end
+
+ # MappingInput
+ test 'input should generate a text area for text attributes' do
+ with_input_for @user, :description, :text
+ assert_select 'textarea.text#user_description'
+ end
+
test 'input should generate a checkbox by default for boolean attributes' do
with_input_for @user, :active, :boolean
assert_select 'input[type=checkbox].boolean#user_active'
end
-
+
test 'input should generate a password field for password attributes' do
with_input_for @user, :password, :password
assert_select 'input[type=password].password#user_password'
end
- test 'input should generate a hidden field' do
- with_input_for @user, :name, :hidden
- assert_no_select 'input[type=text]'
- assert_select 'input#user_name[type=hidden]'
- end
-
test 'input should generate a file field' do
with_input_for @user, :name, :file
assert_select 'input#user_name[type=file]'
end
+ # HiddenInput
+ test 'input should generate a hidden field' do
+ with_input_for @user, :name, :hidden
+ assert_no_select 'input[type=text]'
+ assert_select 'input#user_name[type=hidden]'
+ end
+
+ test 'hint should not be generated for hidden fields' do
+ with_input_for @user, :name, :hidden, :hint => 'Use with care...'
+ assert_no_select 'span.hint'
+ end
+
+ test 'label should not be generated for hidden inputs' do
+ with_input_for @user, :name, :hidden
+ assert_no_select 'label'
+ end
+
+ # PriorityInput
test 'input should generate a country select field' do
with_input_for @user, :country, :country
assert_select 'select#user_country'
assert_select 'select option[value=Brazil]', 'Brazil'
assert_no_select 'select option[value=][disabled=disabled]'
end
-
+
test 'input should generate a country select with simple form default' do
swap SimpleForm, :country_priority => [ 'Brazil' ] do
with_input_for @user, :country, :country
@@ -103,34 +116,35 @@ class InputTest < ActionView::TestCase
assert_select 'select option[value=Brasilia]', '(GMT-03:00) Brasilia'
assert_no_select 'select option[value=][disabled=disabled]'
end
-
+
test 'input should generate a time zone select field with default' do
with_input_for @user, :time_zone, :time_zone, :default => 'Brasilia'
assert_select 'select option[value=Brasilia][selected=selected]'
end
-
+
test 'input should generate a time zone select using options priority' do
with_input_for @user, :time_zone, :time_zone, :priority => /Brasilia/
assert_select 'select option[value=][disabled=disabled]'
end
+ # DateTime input
test 'input should generate a datetime select by default for datetime attributes' do
with_input_for @user, :created_at, :datetime
1.upto(5) do |i|
assert_select "form select.datetime#user_created_at_#{i}i"
end
end
-
+
test 'input should be able to pass options to datetime select' do
with_input_for @user, :created_at, :datetime,
:disabled => true, :prompt => { :year => 'ano', :month => 'mês', :day => 'dia' }
-
+
assert_select 'select.datetime[disabled=disabled]'
assert_select 'select.datetime option', 'ano'
assert_select 'select.datetime option', 'mês'
assert_select 'select.datetime option', 'dia'
end
-
+
test 'input should generate a date select for date attributes' do
with_input_for @user, :born_at, :date
assert_select 'select.date#user_born_at_1i'
@@ -138,17 +152,17 @@ class InputTest < ActionView::TestCase
assert_select 'select.date#user_born_at_3i'
assert_no_select 'select.date#user_born_at_4i'
end
-
+
test 'input should be able to pass options to date select' do
with_input_for @user, :born_at, :date, :as => :date,
:disabled => true, :prompt => { :year => 'ano', :month => 'mês', :day => 'dia' }
-
+
assert_select 'select.date[disabled=disabled]'
assert_select 'select.date option', 'ano'
assert_select 'select.date option', 'mês'
assert_select 'select.date option', 'dia'
end
-
+
test 'input should generate a time select for time attributes' do
with_input_for @user, :delivery_time, :time
assert_select 'input[type=hidden]#user_delivery_time_1i'
@@ -157,28 +171,44 @@ class InputTest < ActionView::TestCase
assert_select 'select.time#user_delivery_time_4i'
assert_select 'select.time#user_delivery_time_5i'
end
-
+
test 'input should be able to pass options to time select' do
with_input_for @user, :delivery_time, :time, :required => true,
:disabled => true, :prompt => { :hour => 'hora', :minute => 'minuto' }
-
+
assert_select 'select.time[disabled=disabled]'
assert_select 'select.time option', 'hora'
assert_select 'select.time option', 'minuto'
end
+ test 'label should point to first option when date input type' do
+ with_input_for :project, :created_at, :date
+ assert_select 'label[for=project_created_at_1i]'
+ end
+
+ test 'label should point to first option when datetime input type' do
+ with_input_for :project, :created_at, :datetime
+ assert_select 'label[for=project_created_at_1i]'
+ end
+
+ test 'label should point to first option when time input type' do
+ with_input_for :project, :created_at, :time
+ assert_select 'label[for=project_created_at_4i]'
+ end
+
+ # CollectionInput
test 'input should generate boolean radio buttons by default for radio types' do
with_input_for @user, :active, :radio
assert_select 'input[type=radio][value=true].radio#user_active_true'
assert_select 'input[type=radio][value=false].radio#user_active_false'
end
-
+
test 'input as radio should generate internal labels by default' do
with_input_for @user, :active, :radio
assert_select 'label[for=user_active_true]', 'Yes'
assert_select 'label[for=user_active_false]', 'No'
end
-
+
test 'input as radio should use i18n to translate internal labels' do
store_translations(:en, :simple_form => { :yes => 'Sim', :no => 'Não' }) do
with_input_for @user, :active, :radio
@@ -186,14 +216,14 @@ class InputTest < ActionView::TestCase
assert_select 'label[for=user_active_false]', 'Não'
end
end
-
+
test 'input should generate a boolean select with options by default for select types' do
with_input_for @user, :active, :select
assert_select 'select.select#user_active'
assert_select 'select option[value=true]', 'Yes'
assert_select 'select option[value=false]', 'No'
end
-
+
test 'input as select should use i18n to translate select boolean options' do
store_translations(:en, :simple_form => { :yes => 'Sim', :no => 'Não' }) do
with_input_for @user, :active, :select
@@ -201,53 +231,53 @@ class InputTest < ActionView::TestCase
assert_select 'select option[value=false]', 'Não'
end
end
-
+
test 'input should allow overriding collection for select types' do
with_input_for @user, :name, :select, :collection => ['Jose', 'Carlos']
assert_select 'select.select#user_name'
assert_select 'select option', 'Jose'
assert_select 'select option', 'Carlos'
end
-
+
test 'input should mark the selected value by default' do
@user.name = "Carlos"
with_input_for @user, :name, :select, :collection => ['Jose', 'Carlos']
assert_select 'select option[selected=selected]', 'Carlos'
end
-
+
test 'input should mark the selected value also when using integers' do
@user.age = 18
with_input_for @user, :age, :select, :collection => 18..60
assert_select 'select option[selected=selected]', '18'
end
-
+
test 'input should automatically set include blank' do
with_input_for @user, :age, :select, :collection => 18..30
assert_select 'select option[value=]', ''
end
-
+
test 'input should not set include blank if otherwise is told' do
with_input_for @user, :age, :select, :collection => 18..30, :include_blank => false
assert_no_select 'select option[value=]', ''
end
-
+
test 'input should not set include blank if prompt is given' do
with_input_for @user, :age, :select, :collection => 18..30, :prompt => "Please select foo"
assert_no_select 'select option[value=]', ''
end
-
+
test 'input should not set include blank if multiple is given' do
with_input_for @user, :age, :select, :collection => 18..30, :input_html => { :multiple => true }
assert_no_select 'select option[value=]', ''
end
-
+
test 'input should detect label and value on collections' do
users = [ setup_new_user(:id => 1, :name => "Jose"), setup_new_user(:id => 2, :name => "Carlos") ]
with_input_for @user, :description, :select, :collection => users
assert_select 'select option[value=1]', 'Jose'
assert_select 'select option[value=2]', 'Carlos'
end
-
+
test 'input should allow overriding collection for radio types' do
with_input_for @user, :name, :radio, :collection => ['Jose', 'Carlos']
assert_select 'input[type=radio][value=Jose]'
@@ -255,13 +285,13 @@ class InputTest < ActionView::TestCase
assert_select 'label.collection_radio', 'Jose'
assert_select 'label.collection_radio', 'Carlos'
end
-
+
test 'input should mark the current radio value by default' do
@user.name = "Carlos"
with_input_for @user, :name, :radio, :collection => ['Jose', 'Carlos']
assert_select 'input[type=radio][value=Carlos][checked=checked]'
end
-
+
test 'input should allow using a collection with text/value arrays' do
with_input_for @user, :name, :radio, :collection => [['Jose', 'jose'], ['Carlos', 'carlos']]
assert_select 'input[type=radio][value=jose]'
@@ -269,7 +299,7 @@ class InputTest < ActionView::TestCase
assert_select 'label.collection_radio', 'Jose'
assert_select 'label.collection_radio', 'Carlos'
end
-
+
test 'input should allow overriding label and value method for collections' do
with_input_for @user, :name, :radio,
:collection => ['Jose' , 'Carlos'],
@@ -280,50 +310,19 @@ class InputTest < ActionView::TestCase
assert_select 'label.collection_radio', 'JOSE'
assert_select 'label.collection_radio', 'CARLOS'
end
-
- test 'input should be required by default' do
- with_input_for @user, :name, :string
- assert_select 'input.required#user_name'
- end
-
- test 'input should allow disabling required' do
- with_input_for @user, :name, :string, :required => false
- assert_no_select 'input.required'
- assert_select 'input.optional#user_name'
- end
-
- test 'input should get options from column definition for string attributes' do
- with_input_for @user, :name, :string
- assert_select 'input.string[maxlength=100]'
- end
-
- test 'input should get options from column definition for decimal attributes' do
- with_input_for @user, :credit_limit, :decimal
- assert_select 'input.decimal[maxlength=15]'
- end
-
- test 'input should get options from column definition for password attributes' do
- with_input_for @user, :password, :password
- assert_select 'input.password[maxlength=100]'
- end
-
- test 'input should not generate options for different attributes' do
- with_input_for @user, :description, :text
- assert_select 'textarea'
- assert_no_select 'textarea[maxlength]'
- end
-
+
+ # With no object
test 'input should be generated properly when object is not present' do
with_input_for :project, :name, :string
assert_select 'input.string.required#project_name'
end
-
+
test 'input as radio should be generated properly when object is not present ' do
with_input_for :project, :name, :radio
assert_select 'input.radio#project_name_true'
assert_select 'input.radio#project_name_false'
end
-
+
test 'input as select with collection should be generated properly when object is not present' do
with_input_for :project, :name, :select, :collection => ['Jose', 'Carlos']
assert_select 'select.select#project_name'
diff --git a/test/simple_form_test.rb b/test/simple_form_test.rb
index 0209d775..662e13d9 100644
--- a/test/simple_form_test.rb
+++ b/test/simple_form_test.rb
@@ -1,7 +1,6 @@
require 'test_helper'
class SimpleFormTest < ActiveSupport::TestCase
-
test 'setup block yields self' do
SimpleForm.setup do |config|
assert_equal SimpleForm, config
diff --git a/test/support/models.rb b/test/support/models.rb
index a1f90fd4..7027d2a0 100644
--- a/test/support/models.rb
+++ b/test/support/models.rb
@@ -12,6 +12,10 @@ class Company < Struct.new(:id, :name)
return all[1..2] if options[:joins]
all
end
+
+ def new_record?
+ false
+ end
end
class Tag < Struct.new(:id, :name)
@@ -32,6 +36,9 @@ class User < OpenStruct
@new_record || false
end
+ def company_attributes=(foo)
+ end
+
def column_for_attribute(attribute)
column_type, limit = case attribute.to_sym
when :name, :status, :password then [:string, 100]