diff --git a/lib/simple_form.rb b/lib/simple_form.rb index 16d470b8..39bce42a 100644 --- a/lib/simple_form.rb +++ b/lib/simple_form.rb @@ -2,7 +2,9 @@ require 'simple_form/action_view_extensions/form_helper' require 'simple_form/action_view_extensions/builder' 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 :RequiredComponent, 'simple_form/required_component' end \ No newline at end of file diff --git a/lib/simple_form/abstract_component.rb b/lib/simple_form/abstract_component.rb deleted file mode 100644 index 4333e03c..00000000 --- a/lib/simple_form/abstract_component.rb +++ /dev/null @@ -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 \ No newline at end of file diff --git a/lib/simple_form/components.rb b/lib/simple_form/components.rb new file mode 100644 index 00000000..94a3f21c --- /dev/null +++ b/lib/simple_form/components.rb @@ -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 \ No newline at end of file diff --git a/lib/simple_form/components/base.rb b/lib/simple_form/components/base.rb new file mode 100644 index 00000000..dc9328a9 --- /dev/null +++ b/lib/simple_form/components/base.rb @@ -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 \ No newline at end of file diff --git a/lib/simple_form/components/error.rb b/lib/simple_form/components/error.rb new file mode 100644 index 00000000..990bb2f3 --- /dev/null +++ b/lib/simple_form/components/error.rb @@ -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 diff --git a/lib/simple_form/components/hint.rb b/lib/simple_form/components/hint.rb new file mode 100644 index 00000000..74ba4b72 --- /dev/null +++ b/lib/simple_form/components/hint.rb @@ -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 diff --git a/lib/simple_form/components/input.rb b/lib/simple_form/components/input.rb new file mode 100644 index 00000000..e30b51a8 --- /dev/null +++ b/lib/simple_form/components/input.rb @@ -0,0 +1,68 @@ +module SimpleForm + module Components + class Input < Base + 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 +end diff --git a/lib/simple_form/components/label.rb b/lib/simple_form/components/label.rb new file mode 100644 index 00000000..7823639e --- /dev/null +++ b/lib/simple_form/components/label.rb @@ -0,0 +1,48 @@ +module SimpleForm + module Components + class Label < Base + extend I18nCache + include RequiredComponent + + def self.translate_required_string + i18n_cache :translate_required_string do + I18n.t(:"simple_form.required.string", :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 + + 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 diff --git a/lib/simple_form/error.rb b/lib/simple_form/error.rb deleted file mode 100644 index 0e530c49..00000000 --- a/lib/simple_form/error.rb +++ /dev/null @@ -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 diff --git a/lib/simple_form/form_builder.rb b/lib/simple_form/form_builder.rb index 0f7c1d01..f29d0b53 100644 --- a/lib/simple_form/form_builder.rb +++ b/lib/simple_form/form_builder.rb @@ -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| diff --git a/lib/simple_form/hint.rb b/lib/simple_form/hint.rb deleted file mode 100644 index a2c3210b..00000000 --- a/lib/simple_form/hint.rb +++ /dev/null @@ -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 diff --git a/lib/simple_form/input.rb b/lib/simple_form/input.rb deleted file mode 100644 index 63207e4d..00000000 --- a/lib/simple_form/input.rb +++ /dev/null @@ -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 diff --git a/lib/simple_form/label.rb b/lib/simple_form/label.rb deleted file mode 100644 index 579f878e..00000000 --- a/lib/simple_form/label.rb +++ /dev/null @@ -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 => - %[#{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 - - 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 diff --git a/test/input_test.rb b/test/input_test.rb index 8abdd525..e40f0d89 100644 --- a/test/input_test.rb +++ b/test/input_test.rb @@ -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 diff --git a/test/label_test.rb b/test/label_test.rb index c404ccf7..6ee9dd7f 100644 --- a/test/label_test.rb +++ b/test/label_test.rb @@ -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