mirror of
https://github.com/heartcombo/simple_form.git
synced 2022-11-09 12:19:26 -05:00
Merge origin/master
This commit is contained in:
commit
d64d55b7f9
21 changed files with 258 additions and 252 deletions
|
@ -1,5 +1,10 @@
|
|||
require 'simple_form/builder_extensions'
|
||||
require 'simple_form/form_helper'
|
||||
require 'simple_form/form_builder'
|
||||
require 'simple_form/action_view_extensions/form_helper'
|
||||
require 'simple_form/action_view_extensions/builder'
|
||||
|
||||
ActionView::Helpers::FormBuilder.send :include, SimpleForm::BuilderExtensions
|
||||
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'
|
||||
end
|
|
@ -1,50 +0,0 @@
|
|||
module SimpleForm
|
||||
class AbstractComponent
|
||||
attr_reader :builder, :attribute, :input_type, :options
|
||||
|
||||
def self.basename
|
||||
@basename ||= name.split("::").last.underscore.to_sym
|
||||
end
|
||||
|
||||
def initialize(builder, attribute, input_type, options)
|
||||
@builder = builder
|
||||
@attribute = attribute
|
||||
@input_type = input_type
|
||||
@options = options
|
||||
end
|
||||
|
||||
def generate
|
||||
return "" unless valid?
|
||||
component_tag(content).to_s
|
||||
end
|
||||
|
||||
def valid?
|
||||
true
|
||||
end
|
||||
|
||||
def template
|
||||
@builder.template
|
||||
end
|
||||
|
||||
def object
|
||||
@builder.object
|
||||
end
|
||||
|
||||
def hidden_input?
|
||||
@input_type == :hidden
|
||||
end
|
||||
|
||||
def basename
|
||||
self.class.basename
|
||||
end
|
||||
|
||||
def component_tag(content)
|
||||
template.content_tag(:span, content, :class => basename)
|
||||
end
|
||||
|
||||
def translate(default='')
|
||||
lookups = [ :"#{@builder.object_name}.#{@attribute}", :"#{@attribute}", default ]
|
||||
I18n.t(lookups.shift, :scope => :"simple_form.#{basename.to_s.pluralize}", :default => lookups)
|
||||
end
|
||||
end
|
||||
end
|
19
lib/simple_form/action_view_extensions/builder.rb
Normal file
19
lib/simple_form/action_view_extensions/builder.rb
Normal file
|
@ -0,0 +1,19 @@
|
|||
module SimpleForm
|
||||
module ActionViewExtensions
|
||||
# A collection of methods required by simple_form but added to rails default form.
|
||||
# This means that you can use such methods outside simple_form context.
|
||||
module Builder
|
||||
def collection_radio(attribute, collection, value_method, text_method, html_options={})
|
||||
collection.inject('') do |result, item|
|
||||
value = item.send value_method
|
||||
text = item.send text_method
|
||||
|
||||
result << radio_button(attribute, value, html_options) <<
|
||||
label("#{attribute}_#{value}", text, :class => "radio")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ActionView::Helpers::FormBuilder.send :include, SimpleForm::ActionViewExtensions::Builder
|
13
lib/simple_form/action_view_extensions/form_helper.rb
Normal file
13
lib/simple_form/action_view_extensions/form_helper.rb
Normal file
|
@ -0,0 +1,13 @@
|
|||
module SimpleForm
|
||||
module ActionViewExtensions
|
||||
module FormHelper
|
||||
def simple_form_for(*args, &block)
|
||||
options = args.extract_options!
|
||||
options[:builder] = SimpleForm::FormBuilder
|
||||
form_for(*(args << options), &block)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ActionView::Base.send :include, SimpleForm::ActionViewExtensions::FormHelper
|
|
@ -1,15 +0,0 @@
|
|||
module SimpleForm
|
||||
# A collection of methods required by simple_form but added to rails default form.
|
||||
# This means that you can use such methods outside simple_form context.
|
||||
module BuilderExtensions
|
||||
def collection_radio(attribute, collection, value_method, text_method, html_options={})
|
||||
collection.inject('') do |result, item|
|
||||
value = item.send value_method
|
||||
text = item.send text_method
|
||||
|
||||
result << radio_button(attribute, value, html_options) <<
|
||||
label("#{attribute}_#{value}", text, :class => "radio")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
9
lib/simple_form/components.rb
Normal file
9
lib/simple_form/components.rb
Normal file
|
@ -0,0 +1,9 @@
|
|||
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'
|
||||
end
|
||||
end
|
52
lib/simple_form/components/base.rb
Normal file
52
lib/simple_form/components/base.rb
Normal file
|
@ -0,0 +1,52 @@
|
|||
module SimpleForm
|
||||
module Components
|
||||
class Base
|
||||
attr_reader :builder, :attribute, :input_type, :options
|
||||
|
||||
def self.basename
|
||||
@basename ||= name.split("::").last.underscore.to_sym
|
||||
end
|
||||
|
||||
def initialize(builder, attribute, input_type, options)
|
||||
@builder = builder
|
||||
@attribute = attribute
|
||||
@input_type = input_type
|
||||
@options = options
|
||||
end
|
||||
|
||||
def generate
|
||||
return "" unless valid?
|
||||
component_tag(content).to_s
|
||||
end
|
||||
|
||||
def valid?
|
||||
true
|
||||
end
|
||||
|
||||
def template
|
||||
@builder.template
|
||||
end
|
||||
|
||||
def object
|
||||
@builder.object
|
||||
end
|
||||
|
||||
def hidden_input?
|
||||
@input_type == :hidden
|
||||
end
|
||||
|
||||
def basename
|
||||
self.class.basename
|
||||
end
|
||||
|
||||
def component_tag(content)
|
||||
template.content_tag(:span, content, :class => basename)
|
||||
end
|
||||
|
||||
def translate(default='')
|
||||
lookups = [ :"#{@builder.object_name}.#{@attribute}", :"#{@attribute}", default ]
|
||||
I18n.t(lookups.shift, :scope => :"simple_form.#{basename.to_s.pluralize}", :default => lookups)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
17
lib/simple_form/components/error.rb
Normal file
17
lib/simple_form/components/error.rb
Normal file
|
@ -0,0 +1,17 @@
|
|||
module SimpleForm
|
||||
module Components
|
||||
class Error < Base
|
||||
def valid?
|
||||
!hidden_input? && !errors.blank?
|
||||
end
|
||||
|
||||
def errors
|
||||
@errors ||= object.errors[@attribute]
|
||||
end
|
||||
|
||||
def content
|
||||
Array(errors).to_sentence
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
13
lib/simple_form/components/hint.rb
Normal file
13
lib/simple_form/components/hint.rb
Normal file
|
@ -0,0 +1,13 @@
|
|||
module SimpleForm
|
||||
module Components
|
||||
class Hint < Base
|
||||
def valid?
|
||||
!hidden_input? && !content.blank?
|
||||
end
|
||||
|
||||
def content
|
||||
@content ||= @options[:hint] || translate
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
68
lib/simple_form/components/input.rb
Normal file
68
lib/simple_form/components/input.rb
Normal file
|
@ -0,0 +1,68 @@
|
|||
module SimpleForm
|
||||
module Components
|
||||
class Input < Base
|
||||
include RequiredHelpers
|
||||
extend I18nCache
|
||||
extend MapType
|
||||
|
||||
map_type :boolean, :to => :check_box
|
||||
map_type :text, :to => :text_area
|
||||
map_type :datetime, :to => :datetime_select, :options => true
|
||||
map_type :date, :to => :date_select, :options => true
|
||||
map_type :time, :to => :time_select, :options => true
|
||||
map_type :password, :to => :password_field
|
||||
map_type :hidden, :to => :hidden_field
|
||||
map_type :select, :to => :collection_select, :options => true, :collection => true
|
||||
map_type :radio, :to => :collection_radio, :collection => true
|
||||
map_type :numeric, :to => :text_field
|
||||
map_type :string, :to => :text_field
|
||||
|
||||
def self.boolean_collection
|
||||
i18n_cache :boolean_collection do
|
||||
[ [I18n.t(:"simple_form.true", :default => 'Yes'), true],
|
||||
[I18n.t(:"simple_form.false", :default => 'No'), false] ]
|
||||
end
|
||||
end
|
||||
|
||||
def generate
|
||||
html_options = @options[:html] || {}
|
||||
html_options[:class] = default_css_classes(html_options[:class])
|
||||
@options[:options] ||= {}
|
||||
|
||||
mapping = self.class.mappings[@input_type]
|
||||
raise "Invalid input type #{@input_type.inspect}" unless mapping
|
||||
|
||||
args = [ @attribute ]
|
||||
|
||||
if mapping.collection
|
||||
collection = @options[:collection] || self.class.boolean_collection
|
||||
detect_collection_methods(collection, @options)
|
||||
args.push(collection, @options[:value_method], @options[:label_method])
|
||||
end
|
||||
|
||||
args << @options[:options] if mapping.options
|
||||
args << html_options
|
||||
|
||||
@builder.send(mapping.method, *args)
|
||||
end
|
||||
|
||||
def detect_collection_methods(collection, options)
|
||||
sample = collection.first
|
||||
|
||||
if sample.is_a?(Array) # TODO Test me
|
||||
options[:label_method] ||= :first
|
||||
options[:value_method] ||= :last
|
||||
elsif sample.is_a?(String) # TODO Test me
|
||||
options[:label_method] ||= :to_s
|
||||
options[:value_method] ||= :to_s
|
||||
elsif sample.is_a?(Numeric) # TODO Test me (including selected)
|
||||
options[:label_method] ||= :to_s
|
||||
options[:value_method] ||= :to_i
|
||||
else # TODO Implement collection label methods or something similar
|
||||
options[:label_method] ||= :to_s
|
||||
options[:value_method] ||= :to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
48
lib/simple_form/components/label.rb
Normal file
48
lib/simple_form/components/label.rb
Normal file
|
@ -0,0 +1,48 @@
|
|||
module SimpleForm
|
||||
module Components
|
||||
class Label < Base
|
||||
include RequiredHelpers
|
||||
extend I18nCache
|
||||
|
||||
def self.translate_required_string
|
||||
i18n_cache :translate_required_string do
|
||||
I18n.t(:"simple_form.required.string", :default =>
|
||||
%[<abbr title="#{translate_required_text}">#{translate_required_mark}</abbr> ]
|
||||
)
|
||||
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
|
||||
|
||||
def generate
|
||||
return '' unless valid?
|
||||
html_options = { :class => default_css_classes }
|
||||
html_options[:for] = @options[:html][:id] if @options.key?(:html)
|
||||
@builder.label(@attribute, label_text, html_options)
|
||||
end
|
||||
|
||||
def label_text
|
||||
required_text << (@options[:label] || translate_label)
|
||||
end
|
||||
|
||||
def required_text
|
||||
attribute_required? ? self.class.translate_required_string.dup : ''
|
||||
end
|
||||
|
||||
def translate_label
|
||||
default = object.try(:human_attribute_name, @attribute.to_s) || @attribute.to_s.humanize
|
||||
translate(default)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,15 +0,0 @@
|
|||
module SimpleForm
|
||||
class Error < AbstractComponent
|
||||
def valid?
|
||||
!hidden_input? && !errors.blank?
|
||||
end
|
||||
|
||||
def errors
|
||||
@errors ||= object.errors[@attribute]
|
||||
end
|
||||
|
||||
def content
|
||||
Array(errors).to_sentence
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,24 +1,17 @@
|
|||
require 'simple_form/abstract_component'
|
||||
require 'simple_form/label'
|
||||
require 'simple_form/input'
|
||||
require 'simple_form/hint'
|
||||
require 'simple_form/error'
|
||||
|
||||
module SimpleForm
|
||||
class FormBuilder < ActionView::Helpers::FormBuilder
|
||||
# Components used by the folder builder. By default is:
|
||||
# [SimpleForm::Label, SimpleForm::Input, SimpleForm::Hint, SimpleForm::Error]
|
||||
# Components used by the folder builder.
|
||||
# By default is [:label, :input, :hint, :error].
|
||||
cattr_accessor :components, :instance_writer => false
|
||||
@@components = [SimpleForm::Label, SimpleForm::Input, SimpleForm::Hint, SimpleForm::Error]
|
||||
@@components = [
|
||||
SimpleForm::Components::Label, SimpleForm::Components::Input,
|
||||
SimpleForm::Components::Hint, SimpleForm::Components::Error
|
||||
]
|
||||
|
||||
# Make the template accessible for components
|
||||
attr_reader :template
|
||||
|
||||
def input(attribute, options={})
|
||||
# TODO Do this makes sense since we are delegating to components?
|
||||
options.assert_valid_keys(:as, :label, :required, :hint, :options, :html,
|
||||
:collection, :label_method, :value_method)
|
||||
|
||||
input_type = (options[:as] || default_input_type(attribute, options)).to_sym
|
||||
|
||||
pieces = self.components.collect do |klass|
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
module SimpleForm
|
||||
module FormHelper
|
||||
|
||||
def simple_form_for(*args, &block)
|
||||
options = args.extract_options!
|
||||
options[:builder] = SimpleForm::FormBuilder
|
||||
form_for(*(args << options), &block)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ActionView::Base.send :include, SimpleForm::FormHelper
|
|
@ -1,11 +0,0 @@
|
|||
module SimpleForm
|
||||
class Hint < AbstractComponent
|
||||
def valid?
|
||||
!hidden_input? && !content.blank?
|
||||
end
|
||||
|
||||
def content
|
||||
@content ||= @options[:hint] || translate
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,71 +0,0 @@
|
|||
require 'simple_form/required_component'
|
||||
require 'simple_form/i18n_cache'
|
||||
require 'simple_form/map_type'
|
||||
|
||||
module SimpleForm
|
||||
class Input < AbstractComponent
|
||||
extend I18nCache
|
||||
extend MapType
|
||||
include RequiredComponent
|
||||
|
||||
map_type :boolean, :to => :check_box
|
||||
map_type :text, :to => :text_area
|
||||
map_type :datetime, :to => :datetime_select, :options => true
|
||||
map_type :date, :to => :date_select, :options => true
|
||||
map_type :time, :to => :time_select, :options => true
|
||||
map_type :password, :to => :password_field
|
||||
map_type :hidden, :to => :hidden_field
|
||||
map_type :select, :to => :collection_select, :options => true, :collection => true
|
||||
map_type :radio, :to => :collection_radio, :collection => true
|
||||
map_type :numeric, :to => :text_field
|
||||
map_type :string, :to => :text_field
|
||||
|
||||
def self.boolean_collection
|
||||
i18n_cache :boolean_collection do
|
||||
[ [I18n.t(:"simple_form.true", :default => 'Yes'), true],
|
||||
[I18n.t(:"simple_form.false", :default => 'No'), false] ]
|
||||
end
|
||||
end
|
||||
|
||||
def generate
|
||||
html_options = @options[:html] || {}
|
||||
html_options[:class] = default_css_classes(html_options[:class])
|
||||
@options[:options] ||= {}
|
||||
|
||||
mapping = self.class.mappings[@input_type]
|
||||
raise "Invalid input type #{@input_type.inspect}" unless mapping
|
||||
|
||||
args = [ @attribute ]
|
||||
|
||||
if mapping.collection
|
||||
collection = @options[:collection] || self.class.boolean_collection
|
||||
detect_collection_methods(collection, @options)
|
||||
args.push(collection, @options[:value_method], @options[:label_method])
|
||||
end
|
||||
|
||||
args << @options[:options] if mapping.options
|
||||
args << html_options
|
||||
|
||||
@builder.send(mapping.method, *args)
|
||||
end
|
||||
|
||||
def detect_collection_methods(collection, options)
|
||||
sample = collection.first
|
||||
|
||||
if sample.is_a?(Array) # TODO Test me
|
||||
options[:label_method] ||= :first
|
||||
options[:value_method] ||= :last
|
||||
elsif sample.is_a?(String) # TODO Test me
|
||||
options[:label_method] ||= :to_s
|
||||
options[:value_method] ||= :to_s
|
||||
elsif sample.is_a?(Numeric) # TODO Test me (including selected)
|
||||
options[:label_method] ||= :to_s
|
||||
options[:value_method] ||= :to_i
|
||||
else # TODO Implement collection label methods or something similar
|
||||
options[:label_method] ||= :to_s
|
||||
options[:value_method] ||= :to_s
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
|
@ -1,49 +0,0 @@
|
|||
require 'simple_form/required_component'
|
||||
require 'simple_form/i18n_cache'
|
||||
|
||||
module SimpleForm
|
||||
class Label < AbstractComponent
|
||||
extend SimpleForm::I18nCache
|
||||
include RequiredComponent
|
||||
|
||||
def self.translate_required_string
|
||||
i18n_cache :translate_required_string do
|
||||
I18n.t(:"simple_form.required.string", :default =>
|
||||
%[<abbr title="#{translate_required_text}">#{translate_required_mark}</abbr> ]
|
||||
)
|
||||
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
|
||||
|
||||
def generate
|
||||
return '' unless valid?
|
||||
html_options = { :class => default_css_classes }
|
||||
html_options[:for] = @options[:html][:id] if @options.key?(:html)
|
||||
@builder.label(@attribute, label_text, html_options)
|
||||
end
|
||||
|
||||
def label_text
|
||||
required_text << (@options[:label] || translate_label)
|
||||
end
|
||||
|
||||
def required_text
|
||||
attribute_required? ? self.class.translate_required_string.dup : ''
|
||||
end
|
||||
|
||||
def translate_label
|
||||
default = object.try(:human_attribute_name, @attribute.to_s) || @attribute.to_s.humanize
|
||||
translate(default)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,5 +1,5 @@
|
|||
module SimpleForm
|
||||
module RequiredComponent
|
||||
module RequiredHelpers
|
||||
def attribute_required?
|
||||
@options[:required] != false
|
||||
end
|
|
@ -3,15 +3,7 @@ require 'test_helper'
|
|||
class InputTest < ActionView::TestCase
|
||||
|
||||
setup do
|
||||
SimpleForm::Input.reset_i18n_cache :boolean_collection
|
||||
end
|
||||
|
||||
test 'input should verify options hash' do
|
||||
assert_raise ArgumentError do
|
||||
simple_form_for @user do |f|
|
||||
concat f.input :name, :invalid_param => true
|
||||
end
|
||||
end
|
||||
SimpleForm::Components::Input.reset_i18n_cache :boolean_collection
|
||||
end
|
||||
|
||||
test 'input should generate a default text field' do
|
||||
|
|
|
@ -3,7 +3,7 @@ require 'test_helper'
|
|||
class LabelTest < ActionView::TestCase
|
||||
|
||||
setup do
|
||||
SimpleForm::Label.reset_i18n_cache :translate_required_string
|
||||
SimpleForm::Components::Label.reset_i18n_cache :translate_required_string
|
||||
end
|
||||
|
||||
test 'input should generate a label with the text field' do
|
||||
|
|
|
@ -13,7 +13,7 @@ I18n.default_locale = :en
|
|||
class ActionView::TestCase
|
||||
include I18nHelper
|
||||
|
||||
tests SimpleForm::FormHelper
|
||||
tests SimpleForm::ActionViewExtensions::FormHelper
|
||||
|
||||
setup :set_controller
|
||||
setup :set_response
|
||||
|
|
Loading…
Reference in a new issue