First work on docs.

This commit is contained in:
Carlos Antonio da Silva 2009-12-10 15:57:24 -02:00
parent c153f10d0d
commit fd490a780b
13 changed files with 345 additions and 8 deletions

View File

@ -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| -%>
<p><%= f.input :username %></p>
<p><%= f.input :password %></p>
<p><%= f.submit 'Save' %></p>
<% 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| -%>
<p><%= f.input :username, :label => 'Your username please' %></p>
<p><%= f.input :password, :hint => 'No special characters.' %></p>
<p><%= f.submit 'Save' %></p>
<% end -%>
Or you can disable labels, hints and errors inside a specific input:
<% simple_form_for @user do |f| -%>
<p><%= f.input :username, :label => false %></p>
<p><%= f.input :password, :hint => false, :error => false %></p>
<p><%= f.input :password_confirmation, :error => false %></p>
<p><%= f.submit 'Save' %></p>
<% end -%>
You can also pass html options for the label, input, hint or error tag:
<% simple_form_for @user do |f| -%>
<p><%= f.input :name, :label_html => { :class => 'my_class' } %></p>
<p><%= f.input :username, :input_html => { :disabled => true } %></p>
<p><%= f.input :password, :hint => 'Confirm!', :hint_html => { :id => 'password_hint' } %></p>
<p><%= f.input :password_confirmation, :error_html => { :id => 'password_error' } %></p>
<p><%= f.submit 'Save' %></p>
<% end -%>
By default all inputs are required, but you can disable it in any input you want:
<% simple_form_for @user do |f| -%>
<p><%= f.input :name, :required => false %></p>
<p><%= f.input :username %></p>
<p><%= f.input :password %></p>
<p><%= f.submit 'Save' %></p>
<% 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| -%>
<p><%= f.input :username %></p>
<p><%= f.input :password %></p>
<p><%= f.input :active, :as => :radio %></p>
<p><%= f.submit 'Save' %></p>
<% 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| -%>
<p><%= f.input :user %></p>
<p><%= f.input :age, :colletion => 18..60 %></p>
<p><%= f.submit 'Save' %></p>
<% 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: '<abbr title="required">*</abbr> '
== 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.

View File

@ -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
end

View File

@ -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
#
# <input id="user_active_true" name="user[active]" type="radio" value="true" />
# <label class="radio" for="user_active_true">Yes</label>
# <input id="user_active_false" name="user[active]" type="radio" value="false" />
# <label class="radio" for="user_active_false">No</label>
#
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

View File

@ -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

View File

@ -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 = []

View File

@ -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?

View File

@ -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?

View File

@ -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

View File

@ -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)

View File

@ -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
end

View File

@ -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):
# <label class="string required" for="user_name"><abbr title="required">*</abbr> Super User Name!</label>
# <input class="string required" id="user_name" maxlength="100" name="user[name]" size="100" type="text" value="New in Simple Form!" />
# <span class="hint">My hint</span>
# <span class="error">can't be blank</span>
#
# Each database type will render a default input, based on some mappings and
# heuristic to determine which is the best option.
# 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

View File

@ -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: '<abbr title="required">*</abbr> '

View File

@ -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
end