From fd490a780be69ef589945e41d9b372b2033ba800 Mon Sep 17 00:00:00 2001 From: Carlos Antonio da Silva Date: Thu, 10 Dec 2009 15:57:24 -0200 Subject: [PATCH] First work on docs. --- README.rdoc | 171 +++++++++++++++++- lib/simple_form.rb | 4 +- .../action_view_extensions/builder.rb | 25 +++ .../action_view_extensions/form_helper.rb | 9 + lib/simple_form/components/base.rb | 35 ++++ lib/simple_form/components/error.rb | 3 + lib/simple_form/components/hint.rb | 3 + lib/simple_form/components/input.rb | 19 ++ lib/simple_form/components/label.rb | 15 ++ lib/simple_form/components/wrapper.rb | 4 +- lib/simple_form/form_builder.rb | 54 ++++++ lib/simple_form/locale/en.yml | 4 +- lib/simple_form/required_helpers.rb | 7 +- 13 files changed, 345 insertions(+), 8 deletions(-) diff --git a/README.rdoc b/README.rdoc index 154030b0..cde9bee9 100644 --- a/README.rdoc +++ b/README.rdoc @@ -1,8 +1,175 @@ -SimpleForm -========== +== SimpleForm Forms made easy! +SimpleForm aims to be as flexible as possible while helping you with powerful components to create your forms. The basic goal of simple form is to not touch your way of defining the layout, this way letting you find how you find the better design for your eyes. + +== Instalation + + sudo gem install simple_form + +== Usage + +SimpleForm was designed to be customized as you need to. Basically it's a stack of components that are generated to create a complete html input for you, with label + hints + errors. The best of this is that you can add any element on this stack in any place, or even remove any of them. + +To start using SimpleForm you just have to use the helper it provides: + + <% simple_form_for @user do |f| -%> +

<%= f.input :username %>

+

<%= f.input :password %>

+

<%= f.submit 'Save' %>

+ <% end -%> + +This will generate an entire form with labels for user name and password as well, and render errors by default when you render the form with invalid data (after submiting for example). + +You can overwrite the default label by passing it to the input method, or even add a hint: + + <% simple_form_for @user do |f| -%> +

<%= f.input :username, :label => 'Your username please' %>

+

<%= f.input :password, :hint => 'No special characters.' %>

+

<%= f.submit 'Save' %>

+ <% end -%> + +Or you can disable labels, hints and errors inside a specific input: + + <% simple_form_for @user do |f| -%> +

<%= f.input :username, :label => false %>

+

<%= f.input :password, :hint => false, :error => false %>

+

<%= f.input :password_confirmation, :error => false %>

+

<%= f.submit 'Save' %>

+ <% end -%> + +You can also pass html options for the label, input, hint or error tag: + + <% simple_form_for @user do |f| -%> +

<%= f.input :name, :label_html => { :class => 'my_class' } %>

+

<%= f.input :username, :input_html => { :disabled => true } %>

+

<%= f.input :password, :hint => 'Confirm!', :hint_html => { :id => 'password_hint' } %>

+

<%= f.input :password_confirmation, :error_html => { :id => 'password_error' } %>

+

<%= f.submit 'Save' %>

+ <% end -%> + +By default all inputs are required, but you can disable it in any input you want: + + <% simple_form_for @user do |f| -%> +

<%= f.input :name, :required => false %>

+

<%= f.input :username %>

+

<%= f.input :password %>

+

<%= f.submit 'Save' %>

+ <% end -%> + +This way the name input will not have the required text and css classes. SimpleForm also lets you overwrite the default input type it creates: + + <% simple_form_for @user do |f| -%> +

<%= f.input :username %>

+

<%= f.input :password %>

+

<%= f.input :active, :as => :radio %>

+

<%= f.submit 'Save' %>

+ <% end -%> + +So instead of a checkbox for the active attribute, you'll have a set of boolean radio buttons with yes/no options. You can do the same with :as => :select option for boolean attributes. + +What if you want to create a select containing the age from 18 to 60 in your form? You can do it overriding the :collection option: + + <% simple_form_for @user do |f| -%> +

<%= f.input :user %>

+

<%= f.input :age, :colletion => 18..60 %>

+

<%= f.submit 'Save' %>

+ <% end -%> + +Collections can be arrays or ranges, and when a :collection is given the :select input will be rendered by default, so we don't need to pass the :as => :select option. + +== Inputs available + +== I18n + +SimpleForm uses all power of I18n API to lookup labels and hints for you. To customize your forms you can create a locale file like this: + + en: + simple_form: + labels: + user: + username: 'User name' + password: 'Password' + hints: + user: + username: 'User name to sign in.' + password: 'No special characters, please.' + +And your forms will use this information to render labels and hints for you. + +SimpleForm also lets you be more specific, separating lookups through actions. Let's say you want a different label and hint for new and edit actions, the locale file would be something like: + + en: + simple_form: + labels: + user: + new: + username: 'User name' + password: 'Password' + edit: + username: 'Change user name' + password: 'Change password' + hints: + user: + new: + username: 'User name to sign in.' + password: 'No special characters, please.' + edit: + username: 'Update your user name to sign in.' + password: 'Let it blank to not change your password.' + +This way SimpleForm will figure out the right translation for you, based on the action being rendered. And to be a little bit DRYer with your locale file, you can skip the model information inside it: + + en: + simple_form: + labels: + username: 'User name' + password: 'Password' + hints: + username: 'User name to sign in.' + password: 'No special characters, please.' + +SimpleForm will always look for a default attribute translation if no specific is found inside the model key. In addition, SimpleForm will fallback to default human_attribute_name from Rails when no other translation is found. + +Finally, you can also overwrite labels and hints inside your view, just by passing the label/hint manually. This way the I18n lookup will be skipped. + +There are other options that can be configured through I18n API, such as required for labels and boolean texts, you just need to overwrite the following keys: + + en: + simple_form: + true: 'Yes' + false: 'No' + required: + text: 'required' + mark: '*' + +Instead of using the text and mark options from required, you can also overwrite the entire required html string as follows: + + en: + simple_form: + required: + string: '* ' + +== Configuration + +You have a set of options available to configure SimpleForm: + +* component_tag => default tag used in components. by default is :span. +* components => stack of components used in form builder to create the input. + You can add or remove any of this components as you need. +* terminator => the last component will call this terminator. By default it's + a lambda returning an empty string. +* collection_label_methods => all methods available to detect the label for a + collection. +* collection_value_methods => all methods available to detect the value for a + collection. +* wrapper_tag => wrapper tag to wrap the inputs. By default no wrapper exists. + +To do it so you just need to create a file inside your initializer folder and use the configurations as follows: + + SimpleForm.collection_label_methods = [:to_label, :title, :description, :name, :to_s] + == TODO Please refer to TODO file. diff --git a/lib/simple_form.rb b/lib/simple_form.rb index 6a97ab46..aaf30db0 100644 --- a/lib/simple_form.rb +++ b/lib/simple_form.rb @@ -8,7 +8,7 @@ module SimpleForm autoload :MapType, 'simple_form/map_type' autoload :RequiredHelpers, 'simple_form/required_helpers' - # Default tag used in componenents. + # Default tag used in components. mattr_accessor :component_tag @@component_tag = :span @@ -35,4 +35,4 @@ module SimpleForm # You can wrap all inputs in a pre-defined tag. By default is nil. mattr_accessor :wrapper_tag @@wrapper_tag = nil -end \ No newline at end of file +end diff --git a/lib/simple_form/action_view_extensions/builder.rb b/lib/simple_form/action_view_extensions/builder.rb index d2cfc6c2..4d3b0efc 100644 --- a/lib/simple_form/action_view_extensions/builder.rb +++ b/lib/simple_form/action_view_extensions/builder.rb @@ -3,6 +3,22 @@ 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 Builder + + # Create a collection of radio inputs for the attribute. Basically this + # helper will create a radio input associated with a label for each + # text/value option in the collection, using value_method and text_method + # to convert these text/value. Based on collection_select. + # Example: + # + # form_for @user do |f| + # f.collection_radio :active, [['Yes', true] ,['No', false]], :first, :last + # end + # + # + # + # + # + # def collection_radio(attribute, collection, value_method, text_method, html_options={}) collection.inject('') do |result, item| value = item.send value_method @@ -13,6 +29,15 @@ module SimpleForm end end + # Wrapper for using simple form inside a default rails form. + # Example: + # + # form_for @user do |f| + # f.simple_fields_for :posts do |posts_form| + # # Here you have all simple_form methods available + # posts_form.input :title + # end + # end def simple_fields_for(*args, &block) options = args.extract_options! options[:builder] = SimpleForm::FormBuilder diff --git a/lib/simple_form/action_view_extensions/form_helper.rb b/lib/simple_form/action_view_extensions/form_helper.rb index e64322ba..10347fcb 100644 --- a/lib/simple_form/action_view_extensions/form_helper.rb +++ b/lib/simple_form/action_view_extensions/form_helper.rb @@ -1,14 +1,23 @@ module SimpleForm module ActionViewExtensions module FormHelper + + # Simple form wrapper around default form_for. + # Example: + # + # simple_form_for @user do |f| + # f.input :name, :hint => 'My hint' + # end def simple_form_for(*args, &block) build_simple_form(:form_for, *args, &block) end + # Wrapper to use simple form with fields_for def simple_fields_for(*args, &block) build_simple_form(:fields_for, *args, &block) end + # Wrapper to use simple form with remote_form_for def simple_remote_form_for(*args, &block) build_simple_form(:remote_form_for, *args, &block) end diff --git a/lib/simple_form/components/base.rb b/lib/simple_form/components/base.rb index 699d2c04..bc68f726 100644 --- a/lib/simple_form/components/base.rb +++ b/lib/simple_form/components/base.rb @@ -20,6 +20,8 @@ module SimpleForm @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 @@ -37,16 +39,49 @@ module SimpleForm self.class.basename 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='') action = template.params[:action] if template.respond_to?(:params) lookups = [] diff --git a/lib/simple_form/components/error.rb b/lib/simple_form/components/error.rb index d15e3066..c832b6fc 100644 --- a/lib/simple_form/components/error.rb +++ b/lib/simple_form/components/error.rb @@ -1,5 +1,8 @@ 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 then the component will be rendered, otherwise will be skipped. class Error < Base def valid? object && !hidden_input? && !errors.blank? diff --git a/lib/simple_form/components/hint.rb b/lib/simple_form/components/hint.rb index 8332c00d..9de8e1d5 100644 --- a/lib/simple_form/components/hint.rb +++ b/lib/simple_form/components/hint.rb @@ -1,5 +1,8 @@ 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? diff --git a/lib/simple_form/components/input.rb b/lib/simple_form/components/input.rb index cec44a78..9d9d5a25 100644 --- a/lib/simple_form/components/input.rb +++ b/lib/simple_form/components/input.rb @@ -1,5 +1,7 @@ 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 @@ -19,6 +21,10 @@ module SimpleForm # Numeric types map_type :integer, :float, :decimal, :to => :text_field + # 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.true", :default => 'Yes'), true], @@ -26,6 +32,8 @@ module SimpleForm end end + # Generate the input through the mapped option. Apply correct behaviors + # for collections and add options whenever the input requires it. def content options[:options] ||= {} mapping = self.class.mappings[input_type] @@ -41,6 +49,9 @@ module SimpleForm protected + # 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) @@ -49,10 +60,13 @@ module SimpleForm 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) args << options[:options] end + # Adds default html options to the input based on db column information. def apply_html_options(args) html_options = component_html_options @@ -63,6 +77,11 @@ module SimpleForm args << html_options 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 diff --git a/lib/simple_form/components/label.rb b/lib/simple_form/components/label.rb index 9dd9a04c..c507f260 100644 --- a/lib/simple_form/components/label.rb +++ b/lib/simple_form/components/label.rb @@ -1,5 +1,11 @@ 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 @@ -28,20 +34,29 @@ module SimpleForm !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(attribute, label_text, html_options) end + # Prepends the required text to label if it is required. The user is able + # to pass a label with the :label option, or it will fallback to label + # lookup. def label_text required_text << (options[:label] || translate_label) end + # Default required text when attribute is required. def required_text attribute_required? ? self.class.translate_required_string.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 = object.try(:human_attribute_name, attribute.to_s) || attribute.to_s.humanize translate(default) diff --git a/lib/simple_form/components/wrapper.rb b/lib/simple_form/components/wrapper.rb index 6449d74c..d06473c0 100644 --- a/lib/simple_form/components/wrapper.rb +++ b/lib/simple_form/components/wrapper.rb @@ -1,5 +1,7 @@ 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 @@ -12,4 +14,4 @@ module SimpleForm end end end -end \ No newline at end of file +end diff --git a/lib/simple_form/form_builder.rb b/lib/simple_form/form_builder.rb index 3a35f26a..146c0930 100644 --- a/lib/simple_form/form_builder.rb +++ b/lib/simple_form/form_builder.rb @@ -3,6 +3,43 @@ module SimpleForm attr_reader :template, :object_name, :object, :attribute, :column, :input_type, :options + # Basic input helper, combines all simple form component stack to generate + # a full component based on options the user define and some guesses through + # database column information. By default a call to input will generate + # label + input + hint (when defined) + errors (when exists), and all can be + # configured inside a wrapper html. + # Examples: + # # Imagine @user has error "can't be blank" on name + # simple_form_for @user do |f| + # f.input :name, :hint => 'My hint' + # end + # + # This is the output html (only the input portion, not the form): + # + # + # My hint + # can't be blank + # + # Each database type will render a default input, based on some mappings and + # heuristic to determine which is the best option. + # You have some options for the input to enable/disable some functions: + # + # :as => allows you to define the input type you want, for instance you + # can use it to generate a text field for a date column. + # :label => when false, no label will be generated. When a string is given, + # it will be used as the label text. Otherwise will lookup i18n. + # :hint => when false, no hint will be generated, even if you have some + # declared using i18n. The string passed will be used as the hint. + # :required => defines whether this attribute is required or not. True + # by default. + # :collection => use to determine a collection that will be used together + # with collection selects or radios. + # :label_html => html options for the label tag + # :input_html => html options for the input tag + # :hint_html => html options for the hint tag + # :error_html => html optinos for the error tag + # :wrapper_html => html options for the wrapper html, when it is configured. + # def input(*args) define_simple_form_attributes(*args) @@ -15,16 +52,28 @@ module SimpleForm component.call end + # Creates an error tag based on the given attribute, only when the attribute + # contains errors. Allows you to pass in all error options, such as + # :error_html. def error(*args) define_simple_form_attributes(*args) SimpleForm::Components::Error.new(self, SimpleForm.terminator).call end + # Creates a hint tag for the given attribute. Requires the :hint option to + # be given, or a hint configured through i18n. Allows using default hint + # options such as :hint_html. def hint(*args) define_simple_form_attributes(*args) SimpleForm::Components::Hint.new(self, SimpleForm.terminator).call end + # Creates a default label tag for the given attribute. You can give a label + # through the :label option, or using i18n, or the attribute name will be + # used. Allows you to pass other label options such as :label_html and + # required. + # When you use it as a default Rails label, ie passing the second parameter + # as string, the label will just delegate to default Rails label. # TODO: as we are overriding default label method, we need a way to call the # default label from rails, or use content tags inside our own helpers. # Check whether we should remove label call, change method name or use content_tag @@ -38,6 +87,7 @@ module SimpleForm private + # Setup default simple form attributes. def define_simple_form_attributes(*args) options = args.extract_options! attribute = args.shift @@ -47,6 +97,9 @@ module SimpleForm @input_type = default_input_type end + # Attempt to guess the better input type given the defined options. By + # default alwayls fallback to the user :as option, or to a :select when a + # collection is given. def default_input_type return @options[:as].to_sym if @options[:as] return :select if @options[:collection] @@ -63,6 +116,7 @@ module SimpleForm end end + # Finds the database column for the given attribute def find_attribute_column @object.column_for_attribute(@attribute) if @object.respond_to?(:column_for_attribute) end diff --git a/lib/simple_form/locale/en.yml b/lib/simple_form/locale/en.yml index 311c0315..93c8fb39 100644 --- a/lib/simple_form/locale/en.yml +++ b/lib/simple_form/locale/en.yml @@ -3,8 +3,8 @@ en: true: 'Yes' false: 'No' required: - text: "required" - mark: "*" + text: 'required' + mark: '*' # If you need to overwrite the html tag, you need to overwrite string. # When using required string, text and mark won't be used. # string: '* ' diff --git a/lib/simple_form/required_helpers.rb b/lib/simple_form/required_helpers.rb index 14180219..c782fa21 100644 --- a/lib/simple_form/required_helpers.rb +++ b/lib/simple_form/required_helpers.rb @@ -1,5 +1,6 @@ module SimpleForm module RequiredHelpers + # Attribute is always required, unless the user has defined the opposite. def attribute_required? options[:required] != false end @@ -8,14 +9,18 @@ module SimpleForm 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 \ No newline at end of file +end