with a `for` attribute pointing to an input that doesn't exist (#253)
+* Fixed that some inputs had invalid 'find_options' attribute (#262)
+* Fixed that we were calling html_safe! when it was not always available
+* Added the ability for :input_html to now accept an option of :size => nil, to exclude the :size attribute altogether (#267)
+
+0.9.10
+
+* Fixed i18n incompatibility with Rails 2.3.8 by reverting two i18n patches pulled in from the rails3 branch
+
+0.9.9
+
+* Changed date/time inputs to default to nil instead of Time.now when the object has no value (due to deprecation warning, #240)
+* Changed the behaviour of associations with a :class_name option to be more consistent with what Rails expects
+* Fixed issues relating to Rails 2.3.6 automatically escaping ERB
+* Fixed issues with Ruby 1.9.1 and Haml
+* Fixed use of deprecated {{key}} syntax in i18n interpolation (thanks to Hans Petter Wilhelmsen)
+* Added the :disabled option to check_boxes input
+* Added translation support for nested models (thanks to Toni Tuominen)
+
0.9.8
* Deprecated :selected/:checked options, see http://wiki.github.com/justinfrench/formtastic/deprecation-of-selected-option
diff --git a/Gemfile b/Gemfile
new file mode 100644
index 000000000..17b2ca7f9
--- /dev/null
+++ b/Gemfile
@@ -0,0 +1,5 @@
+source :rubygems
+
+gem 'rake', '< 0.9'
+
+gemspec
diff --git a/MIT-LICENSE b/MIT-LICENSE
index b32f74184..ef6f718d0 100644
--- a/MIT-LICENSE
+++ b/MIT-LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2008 Justin French
+Copyright (c) 2008-2010 Justin French
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/README.textile b/README.textile
index 461800f0c..6e162fe16 100644
--- a/README.textile
+++ b/README.textile
@@ -2,36 +2,46 @@ h1. Formtastic
Formtastic is a Rails FormBuilder DSL (with some other goodies) to make it far easier to create beautiful, semantically rich, syntactically awesome, readily stylable and wonderfully accessible HTML forms in your Rails applications.
+
+
+
+h2. Compatibility
+
+* Formtastic 2.0 is Rails 3.x compatible only
+* Formtastic 1.x is compatible with both Rails 2 and 3, and is being maintained for bug fixes in the the "1.2-stable branch":https://github.com/justinfrench/formtastic/tree/1.2-stable. View the README in that branch for installation instructions, etc.
+* Formtastic, much like Rails, is very ActiveRecord-centric. Many are successfully using other ActiveModel-like ORMs and objects (DataMapper, MongoMapper, Mongoid, Authlogic, Devise...) but we're not guaranteeing full compatibility at this stage. Patches are welcome!
+
+
h2. The Story
One day, I finally had enough, so I opened up my text editor, and wrote a DSL for how I'd like to author forms:
- <% semantic_form_for @article do |form| %>
-
- <% form.inputs :name => "Basic" do %>
- <%= form.input :title %>
- <%= form.input :body %>
- <%= form.input :section %>
- <%= form.input :publication_state, :as => :radio %>
- <%= form.input :category %>
- <%= form.input :allow_comments, :label => "Allow commenting on this article" %>
+ <%= semantic_form_for @article do |f| %>
+
+ <%= f.inputs :name => "Basic" do %>
+ <%= f.input :title %>
+ <%= f.input :body %>
+ <%= f.input :section %>
+ <%= f.input :publication_state, :as => :radio %>
+ <%= f.input :category %>
+ <%= f.input :allow_comments, :label => "Allow commenting on this article" %>
<% end %>
- <% form.inputs :name => "Advanced" do %>
- <%= form.input :keywords, :required => false, :hint => "Example: ruby, rails, forms" %>
- <%= form.input :extract, :required => false %>
- <%= form.input :description, :required => false %>
- <%= form.input :url_title, :required => false %>
+ <%= f.inputs :name => "Advanced" do %>
+ <%= f.input :keywords, :required => false, :hint => "Example: ruby, rails, forms" %>
+ <%= f.input :extract, :required => false %>
+ <%= f.input :description, :required => false %>
+ <%= f.input :url_title, :required => false %>
<% end %>
- <% form.inputs :name => "Author", :for => :author do |author_form| %>
+ <%= f.inputs :name => "Author", :for => :author do |author_form| %>
<%= author_form.input :first_name %>
<%= author_form.input :last_name %>
<% end %>
- <% form.buttons do %>
- <%= form.commit_button %>
+ <%= f.buttons do %>
+ <%= f.commit_button %>
<% end %>
<% end %>
@@ -40,36 +50,27 @@ One day, I finally had enough, so I opened up my text editor, and wrote a DSL fo
I also wrote the accompanying HTML output I expected, favoring something very similar to the fieldsets, lists and other semantic elements Aaron Gustafson presented in "Learning to Love Forms":http://www.slideshare.net/AaronGustafson/learning-to-love-forms-web-directions-south-07, hacking together enough Ruby to prove it could be done.
-h2. It's better than _SomeOtherFormBuilder_ because...
-
-* it can handle @belongs_to@ associations (like Post belongs_to :author), rendering a select or set of radio inputs with choices from the parent model.
-* it can handle @has_many@ and @has_and_belongs_to_many@ associations (like: Post has_many :tags), rendering a multi-select with choices from the child models.
-* it's Rails 2.3-ready (including nested forms).
-* it has internationalization (I18n)!
-* it's _really_ quick to get started with a basic form in place (4 lines), then go back to add in more detail if you need it.
-* there's heaps of elements, id and class attributes for you to hook in your CSS and JS.
-* it handles real world stuff like inline hints, inline error messages & help text.
-* it doesn't hijack or change any of the standard Rails form inputs, so you can still use them as expected (even mix and match).
-* it's got absolutely awesome spec coverage.
-* there's a bunch of people using and working on it (it's not just one developer building half a solution).
-
+h2. It's awesome because...
-h2. Why?
-
-* web apps = lots of forms.
-* forms are so friggin' boring to code.
-* semantically rich & accessible forms really are possible.
-* the "V" is way behind the "M" and "C" in Rails' MVC – it's the ugly sibling.
-* best practices and common patterns have to start somewhere.
-* i need a challenge.
+* It can handle @belongs_to@ associations (like Post belongs_to :author), rendering a select or set of radio inputs with choices from the parent model.
+* It can handle @has_many@ and @has_and_belongs_to_many@ associations (like: Post has_many :tags), rendering a multi-select with choices from the child models.
+* It's Rails 3 compatible (including nested forms).
+* It has internationalization (I18n)!
+* It's _really_ quick to get started with a basic form in place (4 lines), then go back to add in more detail if you need it.
+* There's heaps of elements, id and class attributes for you to hook in your CSS and JS.
+* It handles real world stuff like inline hints, inline error messages & help text.
+* It doesn't hijack or change any of the standard Rails form inputs, so you can still use them as expected (even mix and match).
+* It's got absolutely awesome spec coverage.
+* There's a bunch of people using and working on it (it's not just one developer building half a solution).
+* It has growing HTML5 support (new inputs like email/phone/search, new attributes like required/min/max/step/placeholder)
h2. Opinions
-* it should be easier to do things the right way than the wrong way.
-* sometimes _more mark-up_ is better.
-* elements and attribute hooks are _gold_ for stylesheet authors.
-* make the common things we do easy, yet still ensure uncommon things are still possible.
+* It should be easier to do things the right way than the wrong way.
+* Sometimes _more mark-up_ is better.
+* Elements and attribute hooks are _gold_ for stylesheet authors.
+* Make the common things we do easy, yet ensure uncommon things are still possible.
h2. Documentation
@@ -79,40 +80,61 @@ RDoc documentation _should_ be automatically generated after each commit and mad
h2. Installation
-The gem is hosted on gemcutter, so *if you haven't already*, add it as a gem source:
+Simply add Formtastic to your Gemfile and bundle it up:
- sudo gem sources -a http://gemcutter.org/
+ gem 'formtastic'
-Then install the Formtastic gem:
+Run the installation generator:
- sudo gem install formtastic
+ $ rails generate formtastic:install
-And add it to your environment.rb configuration as a gem dependency:
+
+h2. Stylesheets
+
+A proof-of-concept set of stylesheets are provided which you can include in your layout. Customization is best achieved by overriding these styles in an additional stylesheet.
+
+h3. Stylesheet usage in Rails < 3.1:
- config.gem 'formtastic'
+ $ rails generate formtastic:install
-Optionally, run @./script/generate formtastic@ to copy the following files into your app:
+
+ # app/views/layouts/application.html.erb
+ <%= stylesheet_link_tag 'formtastic', 'my_formtastic_changes' %>
+
+
+
-* @config/initializers/formtastic.rb@ - a commented out Formtastic config initializer
-* @public/stylesheets/formtastic.css@
-* @public/stylesheets/formtastic_changes.css@
+h3. Stylesheet usage in Rails >= 3.1:
-A proof-of-concept stylesheet is provided which you can include in your layout. Customization is best achieved by overriding these styles in an additional stylesheet so that the Formtastic styles can be updated without clobbering your changes. If you want to use these stylesheets, add both to your layout with this helper:
+Rails 3.1 introduces an asset pipeline that allows plugins like Formtastic to serve their own Stylesheets, Javascripts, etc without having to run generators that copy them accross to the host application. Formtastic makes three stylesheets available as an Engine, you just need to require them in your global stylesheets.
-
- ...
- <%= formtastic_stylesheet_link_tag %>
- ...
-
+ # app/assets/stylesheets/application.css
+ *= require formtastic
+ *= require my_formtastic_changes
+
+ # app/assets/stylesheets/ie6.css
+ *= require formtastic_ie6
+
+ # app/assets/stylesheets/ie7.css
+ *= require formtastic_ie7
+
+ # app/views/layouts/application.html.erb
+ <%= stylesheet_link_tag 'application' %>
+
+
+
+
+
+
h2. Usage
Forms are really boring to code... you want to get onto the good stuff as fast as possible.
@@ -120,34 +142,36 @@ Forms are really boring to code... you want to get onto the good stuff as fast a
This renders a set of inputs (one for _most_ columns in the database table, and one for each ActiveRecord @belongs_to@-association), followed by a submit button:
- <% semantic_form_for @user do |form| %>
- <%= form.inputs %>
- <%= form.buttons %>
+ <%= semantic_form_for @user do |f| %>
+ <%= f.inputs %>
+ <%= f.buttons %>
<% end %>
-If you want to specify the order of the fields, skip some of the fields or even add in fields that Formtastic couldn't detect, you can pass in a list of field names to @inputs@ and list of button names to @buttons@:
+This is a great way to get something up fast, but like scaffolding, it's *not recommended for production*. Don't be so lazy!
+
+To specify the order of the fields, skip some of the fields or even add in fields that Formtastic couldn't infer. You can pass in a list of field names to @inputs@ and list of button names to @buttons@:
- <% semantic_form_for @user do |form| %>
- <%= form.inputs :title, :body, :section, :categories, :created_at %>
- <%= form.buttons :commit %>
+ <%= semantic_form_for @user do |f| %>
+ <%= f.inputs :title, :body, :section, :categories, :created_at %>
+ <%= f.buttons :commit %>
<% end %>
-If you want control over the input type Formtastic uses for each field, you can expand the @inputs@ and @buttons@ blocks. This specifies the @:section@ input should be a set of radio buttons (rather than the default select box), and that the @:created_at@ field should be a string (rather than the default datetime selects):
+You probably want control over the input type Formtastic uses for each field. You can expand the @inputs@ and @buttons@ to block helper format and use the @:as@ option to specify an exact input type:
- <% semantic_form_for @post do |form| %>
- <% form.inputs do %>
- <%= form.input :title %>
- <%= form.input :body %>
- <%= form.input :section, :as => :radio %>
- <%= form.input :categories %>
- <%= form.input :created_at, :as => :string %>
+ <%= semantic_form_for @post do |f| %>
+ <%= f.inputs do %>
+ <%= f.input :title %>
+ <%= f.input :body %>
+ <%= f.input :section, :as => :radio %>
+ <%= f.input :categories %>
+ <%= f.input :created_at, :as => :string %>
<% end %>
- <% form.buttons do %>
- <%= form.commit_button %>
+ <%= f.buttons do %>
+ <%= f.commit_button %>
<% end %>
<% end %>
@@ -155,90 +179,122 @@ If you want control over the input type Formtastic uses for each field, you can
If you want to customize the label text, or render some hint text below the field, specify which fields are required/optional, or break the form into two fieldsets, the DSL is pretty comprehensive:
- <% semantic_form_for @post do |form| %>
- <% form.inputs "Basic", :id => "basic" do %>
- <%= form.input :title %>
- <%= form.input :body %>
+ <%= semantic_form_for @post do |f| %>
+ <%= f.inputs "Basic", :id => "basic" do %>
+ <%= f.input :title %>
+ <%= f.input :body %>
<% end %>
- <% form.inputs :name => "Advanced Options", :id => "advanced" do %>
- <%= form.input :slug, :label => "URL Title", :hint => "Created automatically if left blank", :required => false %>
- <%= form.input :section, :as => :radio %>
- <%= form.input :user, :label => "Author", :label_method => :full_name, %>
- <%= form.input :categories, :required => false %>
- <%= form.input :created_at, :as => :string, :label => "Publication Date", :required => false %>
+ <%= f.inputs :name => "Advanced Options", :id => "advanced" do %>
+ <%= f.input :slug, :label => "URL Title", :hint => "Created automatically if left blank", :required => false %>
+ <%= f.input :section, :as => :radio %>
+ <%= f.input :user, :label => "Author", :member_label => :full_name %>
+ <%= f.input :categories, :required => false %>
+ <%= f.input :created_at, :as => :string, :label => "Publication Date", :required => false %>
<% end %>
- <% form.buttons do %>
- <%= form.commit_button %>
+ <%= f.buttons do %>
+ <%= f.commit_button %>
<% end %>
<% end %>
-Nested forms (Rails 2.3) are also supported (don't forget your models need to be setup correctly with accepts_nested_attributes_for – search the Rails docs). You can do it in the Rails way:
+You can create forms for nested resources:
+
+
+ <%= semantic_form_for [@author, @post] do |f| %>
+
+
+Nested forms are also supported (don't forget your models need to be setup correctly with @accepts_nested_attributes_for@). You can do it in the Rails way:
- <% semantic_form_for @post do |form| %>
- <%= form.inputs :title, :body, :created_at %>
- <% form.semantic_fields_for :author do |author| %>
+ <%= semantic_form_for @post do |f| %>
+ <%= f.inputs :title, :body, :created_at %>
+ <%= f.semantic_fields_for :author do |author| %>
<%= author.inputs :first_name, :last_name, :name => "Author" %>
<% end %>
- <%= form.buttons %>
+ <%= f.buttons %>
<% end %>
Or the Formtastic way with the @:for@ option:
- <% semantic_form_for @post do |form| %>
- <%= form.inputs :title, :body, :created_at %>
- <%= form.inputs :first_name, :last_name, :for => :author, :name => "Author" %>
- <%= form.buttons %>
+ <%= semantic_form_for @post do |f| %>
+ <%= f.inputs :title, :body, :created_at %>
+ <%= f.inputs :first_name, :last_name, :for => :author, :name => "Author" %>
+ <%= f.buttons %>
<% end %>
-When working in has many association, you can even supply @"%i"@ in your fieldset name that it will be properly interpolated with the child index. For example:
+When working in has many association, you can even supply @"%i"@ in your fieldset name; they will be properly interpolated with the child index. For example:
- <% semantic_form_for @post do |form| %>
- <%= form.inputs %>
- <%= form.inputs :name => 'Category #%i', :for => :categories %>
- <%= form.buttons %>
+ <%= semantic_form_for @post do |f| %>
+ <%= f.inputs %>
+ <%= f.inputs :name => 'Category #%i', :for => :categories %>
+ <%= f.buttons %>
<% end %>
+If you have more than one form on the same page, it may lead to HTML invalidation because of the way HTML element id attributes are assigned. You can provide a namespace for your form to ensure uniqueness of id attributes on form elements. The namespace attribute will be prefixed with underscore on the generate HTML id. For example:
+
+
+ <%= semantic_form_for(@post, :namespace => 'cat_form') do |f| %>
+ <%= f.inputs do %>
+ <%= f.input :title %> # id="cat_form_post_title"
+ <%= f.input :body %> # id="cat_form_post_body"
+ <%= f.input :created_at %> # id="cat_form_post_created_at"
+ <% end %>
+ <%= f.buttons %>
+ <% end %>
+
-Customize HTML attributes for any input using the @:input_html@ option. Typically his is used to disable the input, change the size of a text field, change the rows in a textarea, or even to add a special class to an input to attach special behavior like "autogrow":http://plugins.jquery.com/project/autogrow textareas:
+Customize HTML attributes for any input using the @:input_html@ option. Typically this is used to disable the input, change the size of a text field, change the rows in a textarea, or even to add a special class to an input to attach special behavior like "autogrow":http://plugins.jquery.com/project/autogrowtextarea textareas:
- <% semantic_form_for @post do |form| %>
- <%= form.input :title, :input_html => { :size => 60 } %>
- <%= form.input :body, :input_html => { :class => 'autogrow' } %>
- <%= form.input :created_at, :input_html => { :disabled => true } %>
- <%= form.buttons %>
+ <%= semantic_form_for @post do |f| %>
+ <%= f.inputs do %>
+ <%= f.input :title, :input_html => { :cols => 10 } %>
+ <%= f.input :body, :input_html => { :class => 'autogrow', :rows => 10, :cols => 20, :maxlength => 10 } %>
+ <%= f.input :created_at, :input_html => { :disabled => true } %>
+ <% end %>
+ <%= f.buttons %>
<% end %>
The same can be done for buttons with the @:button_html@ option:
- <% semantic_form_for @post do |form| %>
+ <%= semantic_form_for @post do |f| %>
...
- <% form.buttons do %>
- <%= form.commit_button :button_html => { :class => "primary" } %>
+ <%= f.buttons do %>
+ <%= f.commit_button :button_html => { :class => "primary", :disable_with => 'Wait...' } %>
<% end %>
<% end %>
-Customize the HTML attributes for the @ @ wrapper around every input with the @:wrapper_html@ option hash. There's one special key in the hash (@:class@), which will actually _append_ your string of classes to the existing classes provided by Formtastic (like @"required string error"@)
+Customize the HTML attributes for the @ @ wrapper around every input with the @:wrapper_html@ option hash. There's one special key in the hash: (@:class@), which will actually _append_ your string of classes to the existing classes provided by Formtastic (like @"required string error"@).
- <% semantic_form_for @post do |form| %>
- <%= form.input :title, :wrapper_html => { :class => "important" } %>
- <%= form.input :body %>
- <%= form.input :description, :wrapper_html => { :style => "display:none;" } %>
+ <%= semantic_form_for @post do |f| %>
+ <%= f.inputs do %>
+ <%= f.input :title, :wrapper_html => { :class => "important" } %>
+ <%= f.input :body %>
+ <%= f.input :description, :wrapper_html => { :style => "display:none;" } %>
+ <% end %>
...
<% end %>
+Customize the default class used for hints on each attribute or globally in the @config/formtastic.rb@ file. Similarly, you can customize the error classes on an attribute level or globally.
+
+
+ <%= semantic_form_for @post do |f| %>
+ <%= f.inputs do %>
+ <%= f.input :title, :hint_class => 'custom-html-class', :error_class => 'custom-error-class' %>
+ <% end %>
+ <% end %>
+
+
Many inputs provide a collection of options to choose from (like @:select@, @:radio@, @:check_boxes@, @:boolean@). In many cases, Formtastic can find choices through the model associations, but if you want to use your own set of choices, the @:collection@ option is what you want. You can pass in an Array of objects, an array of Strings, a Hash... Throw almost anything at it! Examples:
@@ -272,10 +328,15 @@ The Formtastic input types:
* @:time@ - a time select. Default for column types: @:time@.
* @:boolean@ - a checkbox. Default for column types: @:boolean@.
* @:string@ - a text field. Default for column types: @:string@.
-* @:numeric@ - a text field (just like string). Default for column types: @:integer@, @:float@, and @:decimal@.
+* @:number@ - a text field (just like string). Default for column types: @:integer@, @:float@, and @:decimal@.
* @:file@ - a file field. Default for file-attachment attributes matching: "paperclip":http://github.com/thoughtbot/paperclip or "attachment_fu":http://github.com/technoweenie/attachment_fu.
* @:country@ - a select menu of country names. Default for column types: :string with name @"country"@ - requires a *country_select* plugin to be installed.
+* @:email@ - a text field (just like string). Default for columns with name matching @"email"@. New in HTML5. Works on some mobile browsers already.
+* @:url@ - a text field (just like string). Default for columns with name matching @"url"@. New in HTML5. Works on some mobile browsers already.
+* @:phone@ - a text field (just like string). Default for columns with name matching @"phone"@ or @"fax"@. New in HTML5.
+* @:search@ - a text field (just like string). Default for columns with name matching @"search"@. New in HTML5. Works on Safari.
* @:hidden@ - a hidden field. Creates a hidden field (added for compatibility).
+* @:range@ - a slider field.
The comments in the code are pretty good for each of these (what it does, what the output is, what the options are, etc.) so go check it out.
@@ -295,15 +356,17 @@ h2. Internationalization (I18n)
h3. Basic Localization
-Formtastic got some neat I18n-features. ActiveRecord object names and attributes are, by default, taken from calling @@object.human_name@ and @@object.human_attribute_name(attr)@ respectively. There are a few words specific to Formtastic that can be translated. See @lib/locale/en.yml@ for more information.
+Formtastic has some neat I18n-features. ActiveRecord object names and attributes are, by default, taken from calling @@object.human_name@ and @@object.human_attribute_name(attr)@ respectively. There are a few words specific to Formtastic that can be translated. See @lib/locale/en.yml@ for more information.
Basic localization (labels only, with ActiveRecord):
- <% semantic_form_for @post do |form| %>
- <%= form.input :title %> # => :label => I18n.t('activerecord.attributes.user.title') or 'Title'
- <%= form.input :body %> # => :label => I18n.t('activerecord.attributes.user.body') or 'Body'
- <%= form.input :section %> # => :label => I18n.t('activerecord.attributes.user.section') or 'Section'
+ <%= semantic_form_for @post do |f| %>
+ <%= f.inputs do %>
+ <%= f.input :title %> # => :label => I18n.t('activerecord.attributes.user.title') or 'Title'
+ <%= f.input :body %> # => :label => I18n.t('activerecord.attributes.user.body') or 'Body'
+ <%= f.input :section %> # => :label => I18n.t('activerecord.attributes.user.section') or 'Section'
+ <% end %>
<% end %>
@@ -316,7 +379,7 @@ Formtastic supports localized *labels*, *hints*, *legends*, *actions* using the
*1. Enable I18n lookups by default (@config/initializers/formtastic.rb@):*
- Formtastic::SemanticFormBuilder.i18n_lookups_by_default = true
+ Formtastic::FormBuilder.i18n_lookups_by_default = true
*2. Add some cool label-translations/variants (@config/locale/en.yml@):*
@@ -328,33 +391,37 @@ Formtastic supports localized *labels*, *hints*, *legends*, *actions* using the
post_details: "Post details"
labels:
post:
- title: "Choose a title..."
+ title: "Your Title"
body: "Write something..."
edit:
title: "Edit title"
hints:
post:
- title: "Choose a good title for you post."
+ title: "Choose a good title for your post."
body: "Write something inspiring here."
+ placeholders:
+ post:
+ title: "Title your post"
+ slug: "Leave blank for an automatically generated slug"
+ user:
+ email: "you@yours.com"
actions:
- create: "Create my {{model}}"
+ create: "Create my %{model}"
update: "Save changes"
dummie: "Launch!"
-*Note:* We are using English here still, but you get the point.
-
*3. ...and now you'll get:*
- <% semantic_form_for Post.new do |form| %>
- <% form.inputs do %>
- <%= form.input :title %> # => :label => "Choose a title...", :hint => "Choose a good title for you post."
- <%= form.input :body %> # => :label => "Write something...", :hint => "Write something inspiring here."
- <%= form.input :section %> # => :label => I18n.t('activerecord.attributes.user.section') or 'Section'
+ <%= semantic_form_for Post.new do |f| %>
+ <%= f.inputs do %>
+ <%= f.input :title %> # => :label => "Choose a title...", :hint => "Choose a good title for your post."
+ <%= f.input :body %> # => :label => "Write something...", :hint => "Write something inspiring here."
+ <%= f.input :section %> # => :label => I18n.t('activerecord.attributes.user.section') or 'Section'
<% end %>
- <% form.buttons do %>
- <%= form.commit_button %> # => "Create my {{model}}"
+ <%= f.buttons do %>
+ <%= f.commit_button %> # => "Create my %{model}"
<% end %>
<% end %>
@@ -364,8 +431,8 @@ Formtastic supports localized *labels*, *hints*, *legends*, *actions* using the
_Note: Slightly different because Formtastic can't guess how you group fields in a form. Legend text can be set with first (as in the sample below) specified value, or :name/:title options - depending on what flavor is preferred._
- <% semantic_form_for @post do |form| %>
- <% form.inputs :post_details do %> # => :title => "Post details"
+ <%= semantic_form_for @post do |f| %>
+ <%= f.inputs :post_details do %> # => :title => "Post details"
# ...
<% end %>
# ...
@@ -375,14 +442,14 @@ _Note: Slightly different because Formtastic can't guess how you group fields in
*5. Override I18n settings:*
- <% semantic_form_for @post do |form| %>
- <% form.inputs do %>
- <%= form.input :title %> # => :label => "Choose a title...", :hint => "Choose a good title for you post."
- <%= form.input :body, :hint => false %> # => :label => "Write something..."
- <%= form.input :section, :label => 'Some section' %> # => :label => 'Some section'
+ <%= semantic_form_for @post do |f| %>
+ <%= f.inputs do %>
+ <%= f.input :title %> # => :label => "Choose a title...", :hint => "Choose a good title for your post."
+ <%= f.input :body, :hint => false %> # => :label => "Write something..."
+ <%= f.input :section, :label => 'Some section' %> # => :label => 'Some section'
<% end %>
- <% form.buttons do %>
- <%= form.commit_button :dummie %> # => "Launch!"
+ <%= f.buttons do %>
+ <%= f.commit_button :dummie %> # => "Launch!"
<% end %>
<% end %>
@@ -390,27 +457,27 @@ _Note: Slightly different because Formtastic can't guess how you group fields in
If I18n-lookups is disabled, i.e.:
- Formtastic::SemanticFormBuilder.i18n_lookups_by_default = false
+ Formtastic::FormBuilder.i18n_lookups_by_default = false
...then you can enable I18n within the forms instead:
- <% semantic_form_for @post do |form| %>
- <% form.inputs do %>
- <%= form.input :title, :label => true %> # => :label => "Choose a title..."
- <%= form.input :body, :label => true %> # => :label => "Write something..."
- <%= form.input :section, :label => true %> # => :label => I18n.t('activerecord.attributes.user.section') or 'Section'
+ <%= semantic_form_for @post do |f| %>
+ <%= f.inputs do %>
+ <%= f.input :title, :label => true %> # => :label => "Choose a title..."
+ <%= f.input :body, :label => true %> # => :label => "Write something..."
+ <%= f.input :section, :label => true %> # => :label => I18n.t('activerecord.attributes.user.section') or 'Section'
<% end %>
- <% form.buttons do %>
- <%= form.commit_button true %> # => "Save changes" (if we are in edit that is...)
+ <%= f.buttons do %>
+ <%= f.commit_button true %> # => "Update %{model}" (if we are in edit that is...)
<% end %>
<% end %>
*6. Advanced I18n lookups*
-For more flexible forms; Formtastic find translations using a bottom-up approach taking the following variables in account:
+For more flexible forms; Formtastic finds translations using a bottom-up approach taking the following variables in account:
* @MODEL@, e.g. "post"
* @ACTION@, e.g. "edit"
@@ -448,127 +515,109 @@ h2. Semantic errors
You can show errors on base (by default) and any other attribute just passing it name to semantic_errors method:
- <% semantic_form_for @post do |form| %>
- <%= form.semantic_errors :state %>
+ <%= semantic_form_for @post do |f| %>
+ <%= f.semantic_errors :state %>
<% end %>
-h2. ValidationReflection plugin
-
-If you have the "ValidationReflection":http://github.com/redinger/validation_reflection plugin installed, you won't have to specify the @:required@ option (it checks the validations on the model instead).
-
+h2. Modified & Custom Inputs
-h2. Configuration
+You can modify existing inputs, subclass them, or create your own from scratch. Here's the basic process:
-Run @./script/generate formtastic@ to copy a commented out config file into @config/initializers/formtastic.rb@. You can "view the configuration file on GitHub":http://github.com/justinfrench/formtastic/blob/master/generators/formtastic/templates/formtastic.rb
+* Create a file in @app/inputs@ with a filename ending in @_input.rb@. For example, @app/inputs/hat_size_input.rb@. Formtastic will automatically look in @app/inputs@ and find the file.
+* In that file, declare a classname ending in @Input@. For example, @class HatSizeInput@. It must have a @to_html@ method for rendering.
+* To use that input, leave off the word "input" in your @as@ statement. For example, @f.input(:size, :as => :hat_size)@
+Specific examples follow.
-h2. Form Generator
+h3. Changing Existing Input Behavior
-There's a Formtastic form code generator to make your transition to Formtastic easier. All you have to do is to *specify an existing model name*, and optionally specify view template framework (ERB/HAML), and you are good to go. *Note:* This won't overwrite any of you stuff. This is how you use it:
-
-*Alt. 1: Generate in terminal:*
+To modify the behavior of @StringInput@, subclass it in a new file, @app/inputs/string_input.rb@:
-$ ./script/generate form Post
-# ---------------------------------------------------------
-# GENERATED FORMTASTIC CODE
-# ---------------------------------------------------------
+ class StringInput < Formtastic::Inputs::StringInput
+ def to_html
+ puts "this is my modified version of StringInput"
+ super
+ end
+ end
+
-<% f.inputs do %>
- <%= f.input :title, :label => 'Title' %>
- <%= f.input :body, :label => 'Body' %>
- <%= f.input :published, :label => 'Published' %>
-<% end %>
+You can use your modified version with @:as => :string@.
-# ---------------------------------------------------------
- Copied to clipboard - just paste it!
-
+h3. Creating New Inputs Based on Existing Ones
-*Alt. 2: Generate partial:*
+To create your own new types of inputs based on existing inputs, the process is similar. For example, to create @FlexibleTextInput@ based on @StringInput@, put the following in @app/inputs/flexible_text_input.rb@:
-$ ./script/generate form Post --partial
- exists app/views/posts
- create app/views/posts/_form.html.erb
+ class FlexibleTextInput < Formtastic::Inputs::StringInput
+ def input_html_options
+ super.merge(:class => "flexible-text-area")
+ end
+ end
-To generate *HAML* markup, just add the @--haml@ as argument:
+You can use your new input with @:as => :flexible_text@.
-
-$ ./script/generate form Post --haml
- exists app/views/admin/posts
- create app/views/admin/posts/_form.html.haml
-
+h3. Creating New Inputs From Scratch
-To specify the controller in a namespace (eg admin/posts instead of posts), use the --controller argument:
+To create a custom @DatePickerInput@ from scratch, put the following in @app/inputs/date_picker_input.rb@:
-$ ./script/generate form Post --partial --controller admin/posts
- exists app/views/admin/posts
- create app/views/admin/posts/_form.html.erb
+ class DatePickerInput
+ include Formtastic::Inputs::Base
+ def to_html
+ # ...
+ end
+ end
+You can use your new input with @:as => :date_picker@.
-h2. Custom Inputs
-
-If you want to add your own input types to encapsulate your own logic or interface patterns, you can do so by subclassing SemanticFormBuilder and configuring Formtastic to use your custom builder class.
+h3. Don't subclass Formtastic::FormBuilder anymore
-@Formtastic::SemanticFormHelper.builder = MyCustomBuilder@
+It was previously recommended in Formtastic 1.x to subclass Formtastic::FormBuilder to add your own inputs. This is no longer recommended in Formtastic 2, and will not work as expected.
+h2. Security
+By default, Formtastic escapes HTML entities in both labels and hints unless a string is marked as html_safe. If you are using an older rails version which doesn't know html_safe, or you want to globally turn this feature off, you can set the following in your initializer:
-h2. Status
-
-Formtastic has been in active development for about a year. We've just recently jumped to an 0.9 version number, signaling that we consider this a 1.0 release candidate, and that the API won't change significantly for the 1.x series.
+Formtastic::FormBuilder.escape_html_entities_in_hints_and_labels = false
h2. Dependencies
There are none, but...
-* if you have the "ValidationReflection":http://github.com/redinger/validation_reflection plugin is installed, you won't have to specify the @:required@ option (it checks the validations on the model instead).
-* if you want to use the @:country@ input, you'll need to install the "iso-3166-country-select plugin":http://github.com/rails/iso-3166-country-select (or any other country_select plugin with the same API).
+* If you want to use the @:country@ input, you'll need to install the "country-select plugin":https://github.com/chrislerum/country_select (or any other country_select plugin with the same API).
* "rspec":http://github.com/dchelimsky/rspec/, "rspec_hpricot_matchers":http://rubyforge.org/projects/rspec-hpricot/ and "rcov":http://github.com/relevance/rcov gems (plus any of their own dependencies) are required for the test suite.
-h2. Compatibility
-
-I'm only testing Formtastic with the latest Rails 2.4.x stable release, and it should be fine under Rails 2.3.x as well (including nested forms). Patches are welcome to allow backwards compatibility, but I don't have the energy!
-
-h2. Got TextMate?
-
-Well...there's a TextMate-bundle in town, dedicated to make usage of Formtastic in the "TextMate":http://macromates.com/ editor even more of a breeze:
-
-"Formtastic.tmbundle":http://github.com/grimen/formtastic_tmbundle
-
-
h2. How to contribute
-*Before you send a pull request*, please ensure that you provide appropriate spec/test coverage and ensure the documentation is up-to-date. Bonus points if you perform your changes in a clean topic branch rather than master.
+Please ensure that you provide appropriate spec/test coverage and ensure the documentation is up-to-date. Bonus points if you perform your changes in a clean topic branch rather than master, and if you create a pull request for your changes to be discussed and reviewed.
Please also keep your commits *atomic* so that they are more likely to apply cleanly. That means that each commit should contain the smallest possible logical change. Don't commit two features at once, don't update the gemspec at the same time you add a feature, don't fix a whole bunch of whitespace in a file at the same time you change a few lines, etc, etc.
For significant changes, you may wish to discuss your idea on the Formtastic Google group before coding to ensure that your change is likely to be accepted. Formtastic relies heavily on i18n, so if you're unsure of the impact this has on your changes, please discuss them with the group.
+See below for installation of a development environment.
-h2. Maintainers & Contributors
-Formtastic is maintained by "Justin French":http://justinfrench.com, "José Valim":http://github.com/josevalim and "Jonas Grimfelt":http://github.com/grimen, but it wouldn't be as awesome as it is today without help from over 40 contributors.
-
-@git shortlog -n -s --no-merges@
-
-
-h2. Google Group
+h2. Google Group, Twitter, etc
Please join the "Formtastic Google Group":http://groups.google.com.au/group/formtastic, especially if you'd like to talk about a new feature, or report a bug.
+You can also "follow @formtastic on Twitter":http://twitter.com/formtastic for announcements, tutorials and awesome Formtastic links.
h2. Project Info
-Formtastic is hosted on Github: "http://github.com/justinfrench/formtastic":http://github.com/justinfrench/formtastic, where your contributions, forkings, comments and feedback are greatly welcomed.
+Formtastic was created by "Justin French":http://www.justinfrench.com with contributions from over 100 awesome developers.
+
+Run @git shortlog -n -s@ to see the awesome.
+The project is hosted on Github: "http://github.com/justinfrench/formtastic":http://github.com/justinfrench/formtastic, where your contributions, forkings, comments, issues and feedback are greatly welcomed.
-Copyright (c) 2007-2008 Justin French, released under the MIT license.
+Copyright (c) 2007-2010 Justin French, released under the MIT license.
diff --git a/RELEASE_PROCESS b/RELEASE_PROCESS
index 6780d04b3..59bc314d9 100644
--- a/RELEASE_PROCESS
+++ b/RELEASE_PROCESS
@@ -1,11 +1,6 @@
-git status # check you have a clean working directory
-rake version:bump:minor # or patch or major, commits the change
-rake gemspec # to generate the new gemspec file
-git add formtastic.gemspec # stage changes
-git commit -am "new gemspec" # commit and describe the reason for the new gem
-git tag -am "0.2.3" 0.2.3 # tag the new version in the code base too
-git log 0.2.2..0.2.3 # check the log since last tag
-gem build formtastic.gemspec # build the gem
-gem push formtastic-0.2.3.gem # publish the gem
-git push # push to github
-git push --tags # push the tags up to remote too
+# edit version.rb
+git ci -am "version bump" # commit changes
+git tag X.X.X # tag the new version in the code base too
+gem build formtastic.gemspec # build the gem
+gem push formtastic-X.X.X.gem # publish the gem
+git push && git push --tags # push to remote
diff --git a/Rakefile b/Rakefile
index 6a57cbc2a..1d65223c9 100644
--- a/Rakefile
+++ b/Rakefile
@@ -1,71 +1,12 @@
-# coding: utf-8
+# encoding: utf-8
+require 'rubygems'
require 'rake'
require 'rake/rdoctask'
+require 'rspec/core/rake_task'
+require 'tasks/verify_rcov'
+require 'bundler'
-begin
- require 'spec/rake/spectask'
-rescue LoadError
- begin
- gem 'rspec-rails', '>= 1.0.0'
- require 'spec/rake/spectask'
- rescue LoadError
- puts "[formtastic:] RSpec - or one of it's dependencies - is not available. Install it with: sudo gem install rspec-rails"
- end
-end
-
-begin
- GEM = "formtastic"
- AUTHOR = "Justin French"
- EMAIL = "justin@indent.com.au"
- SUMMARY = "A Rails form builder plugin/gem with semantically rich and accessible markup"
- HOMEPAGE = "http://github.com/justinfrench/formtastic/tree/master"
- INSTALL_MESSAGE = %q{
- ========================================================================
- Thanks for installing Formtastic!
- ------------------------------------------------------------------------
- You can now (optionally) run the generator to copy some stylesheets and
- a config initializer into your application:
- ./script/generate formtastic
-
- To generate some semantic form markup for your existing models, just run:
- ./script/generate form MODEL_NAME
-
- Find out more and get involved:
- http://github.com/justinfrench/formtastic
- http://groups.google.com.au/group/formtastic
- ========================================================================
- }
-
- gem 'jeweler', '>= 1.0.0'
- require 'jeweler'
-
- Jeweler::Tasks.new do |s|
- s.name = GEM
- s.summary = SUMMARY
- s.email = EMAIL
- s.homepage = HOMEPAGE
- s.description = SUMMARY
- s.author = AUTHOR
- s.post_install_message = INSTALL_MESSAGE
-
- s.require_path = 'lib'
- s.files = %w(MIT-LICENSE README.textile Rakefile) + Dir.glob("{rails,lib,generators,spec}/**/*")
-
- # Runtime dependencies: When installing Formtastic these will be checked if they are installed.
- # Will be offered to install these if they are not already installed.
- s.add_dependency 'activesupport', '>= 2.3.0'
- s.add_dependency 'actionpack', '>= 2.3.0'
-
- # Development dependencies. Not installed by default.
- # Install with: sudo gem install formtastic --development
- s.add_development_dependency 'rspec-rails', '>= 1.2.6'
- s.add_development_dependency 'rspec_tag_matchers', '>= 1.0.0'
- end
-
- Jeweler::GemcutterTasks.new
-rescue LoadError
- puts "[formtastic:] Jeweler - or one of its dependencies - is not available. Install it with: sudo gem install jeweler -s http://gemcutter.org"
-end
+Bundler::GemHelper.install_tasks
desc 'Default: run unit specs.'
task :default => :spec
@@ -79,23 +20,33 @@ Rake::RDocTask.new(:rdoc) do |rdoc|
rdoc.rdoc_files.include('lib/**/*.rb')
end
-if defined?(Spec)
- desc 'Test the formtastic plugin.'
- Spec::Rake::SpecTask.new('spec') do |t|
- t.spec_files = FileList['spec/**/*_spec.rb']
- t.spec_opts = ["-c"]
- end
+desc 'Test the formtastic plugin.'
+RSpec::Core::RakeTask.new('spec') do |t|
+ t.pattern = FileList['spec/**/*_spec.rb']
+end
- desc 'Test the formtastic plugin with specdoc formatting and colors'
- Spec::Rake::SpecTask.new('specdoc') do |t|
- t.spec_files = FileList['spec/**/*_spec.rb']
- t.spec_opts = ["--format specdoc", "-c"]
- end
+desc 'Test the formtastic inputs.'
+RSpec::Core::RakeTask.new('spec:inputs') do |t|
+ t.pattern = FileList['spec/inputs/*_spec.rb']
+end
+
+desc 'Test the formtastic plugin with specdoc formatting and colors'
+RSpec::Core::RakeTask.new('specdoc') do |t|
+ t.pattern = FileList['spec/**/*_spec.rb']
+end
+
+desc 'Run all examples with RCov'
+RSpec::Core::RakeTask.new('rcov') do |t|
+ t.pattern = FileList['spec/**/*_spec.rb']
+ t.rcov = true
+ t.rcov_opts = %w(--exclude gems/*,spec/*,.bundle/*, --aggregate coverage.data)
+end
+
+RCov::VerifyTask.new(:verify_coverage) do |t|
+ t.require_exact_threshold = false
+ t.threshold = (RUBY_VERSION == "1.8.7" ? 95 : 0)
+end
- desc "Run all examples with RCov"
- Spec::Rake::SpecTask.new('examples_with_rcov') do |t|
- t.spec_files = FileList['spec/**/*_spec.rb']
- t.rcov = true
- t.rcov_opts = ['--exclude', 'spec,Library']
- end
+desc "Run all examples and verify coverage"
+task :spec_and_verify_coverage => [:rcov, :verify_coverage] do
end
diff --git a/VERSION.yml b/VERSION.yml
deleted file mode 100644
index 29be119c7..000000000
--- a/VERSION.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-:major: 0
-:minor: 9
-:build:
-:patch: 8
diff --git a/app/assets/stylesheets/formtastic.css b/app/assets/stylesheets/formtastic.css
new file mode 100644
index 000000000..e06160e09
--- /dev/null
+++ b/app/assets/stylesheets/formtastic.css
@@ -0,0 +1,275 @@
+/* -------------------------------------------------------------------------------------------------
+
+It's *strongly* suggested that you don't modify this file. Instead, load a new stylesheet after
+this one in your layouts (eg formtastic_changes.css) and override the styles to suit your needs.
+This will allow you to update formtastic.css with new releases without clobbering your own changes.
+
+This stylesheet forms part of the Formtastic Rails Plugin
+(c) 2008-2011 Justin French
+
+--------------------------------------------------------------------------------------------------*/
+
+/* NORMALIZE AND RESET - obviously inspired by Yahoo's reset.css, but scoped to just .formtastic
+--------------------------------------------------------------------------------------------------*/
+.formtastic,
+.formtastic ul,
+.formtastic ol,
+.formtastic li,
+.formtastic fieldset,
+.formtastic legend,
+.formtastic input,
+.formtastic textarea,
+.formtastic select,
+.formtastic p {
+ margin:0;
+ padding:0;
+}
+
+.formtastic fieldset {
+ border:0;
+}
+
+.formtastic em,
+.formtastic strong {
+ font-style:normal;
+ font-weight:normal;
+}
+
+.formtastic ol,
+.formtastic ul {
+ list-style:none;
+}
+
+.formtastic abbr,
+.formtastic acronym {
+ border:0;
+ font-variant:normal;
+}
+
+.formtastic input,
+.formtastic textarea {
+ font-family:sans-serif;
+ font-size:inherit;
+ font-weight:inherit;
+}
+
+.formtastic input,
+.formtastic textarea,
+.formtastic select {
+ font-size:100%;
+}
+
+.formtastic legend {
+ white-space:normal;
+ color:#000;
+}
+
+
+/* SEMANTIC ERRORS
+--------------------------------------------------------------------------------------------------*/
+.formtastic .errors {
+ color:#cc0000;
+ margin:0.5em 0 1.5em 25%;
+ list-style:square;
+}
+
+.formtastic .errors li {
+ padding:0;
+ border:none;
+ display:list-item;
+}
+
+
+/* BUTTONS
+--------------------------------------------------------------------------------------------------*/
+.formtastic .buttons {
+ overflow:hidden; /* clear containing floats */
+ padding-left:25%;
+}
+
+.formtastic .button {
+ float:left;
+ padding-right:0.5em;
+}
+
+
+/* INPUTS
+--------------------------------------------------------------------------------------------------*/
+.formtastic .inputs {
+ overflow:hidden; /* clear containing floats */
+}
+
+.formtastic .input {
+ overflow:hidden; /* clear containing floats */
+ padding:0.5em 0; /* padding and negative margin juggling is for Firefox */
+ margin-top:-0.5em;
+ margin-bottom:1em;
+}
+
+
+/* LEFT ALIGNED LABELS
+--------------------------------------------------------------------------------------------------*/
+.formtastic .input .label {
+ display:block;
+ width:25%;
+ float:left;
+ padding-top:.2em;
+}
+
+.formtastic .fragments .label,
+.formtastic .choices .label {
+ position:absolute;
+ width:95%;
+ left:0px;
+}
+
+.formtastic .fragments .label label,
+.formtastic .choices .label label {
+ position:absolute;
+}
+
+/* NESTED FIELDSETS AND LEGENDS (radio, check boxes and date/time inputs use nested fieldsets)
+--------------------------------------------------------------------------------------------------*/
+.formtastic .choices {
+ position:relative;
+}
+
+.formtastic .choices-group {
+ float:left;
+ width:74%;
+ margin:0;
+ padding:0 0 0 25%;
+}
+
+.formtastic .choice {
+ padding:0;
+ border:0;
+}
+
+
+/* INLINE HINTS
+--------------------------------------------------------------------------------------------------*/
+.formtastic .input .inline-hints {
+ color:#666;
+ margin:0.5em 0 0 25%;
+}
+
+
+/* INLINE ERRORS
+--------------------------------------------------------------------------------------------------*/
+.formtastic .inline-errors {
+ color:#cc0000;
+ margin:0.5em 0 0 25%;
+}
+
+.formtastic .errors {
+ color:#cc0000;
+ margin:0.5em 0 0 25%;
+ list-style:square;
+}
+
+.formtastic .errors li {
+ padding:0;
+ border:none;
+ display:list-item;
+}
+
+
+/* STRING, NUMERIC, PASSWORD, EMAIL, URL, PHONE, SEARCH (ETC) OVERRIDES
+--------------------------------------------------------------------------------------------------*/
+.formtastic .stringish input {
+ width:72%;
+}
+
+.formtastic .stringish input[size] {
+ width:auto;
+ max-width:72%;
+}
+
+
+/* TEXTAREA OVERRIDES
+--------------------------------------------------------------------------------------------------*/
+.formtastic .text textarea {
+ width:72%;
+}
+
+.formtastic .text textarea[cols] {
+ width:auto;
+ max-width:72%;
+}
+
+
+/* HIDDEN OVERRIDES
+--------------------------------------------------------------------------------------------------*/
+.formtastic .hidden {
+ display:none;
+}
+
+
+/* BOOLEAN LABELS
+--------------------------------------------------------------------------------------------------*/
+.formtastic .boolean label {
+ padding-left:25%;
+ display:block;
+}
+
+
+/* CHOICE GROUPS
+--------------------------------------------------------------------------------------------------*/
+.formtastic .choices-group {
+ margin-bottom:-0.5em;
+}
+
+.formtastic .choice {
+ margin:0.1em 0 0.5em 0;
+}
+
+.formtastic .choice label {
+ float:none;
+ width:100%;
+ line-height:100%;
+ padding-top:0;
+ margin-bottom:0.6em;
+}
+
+
+/* ADJUSTMENTS FOR INPUTS INSIDE LABELS (boolean input, radio input, check_boxes input)
+--------------------------------------------------------------------------------------------------*/
+.formtastic .choice label input,
+.formtastic .boolean label input {
+ margin:0 0.3em 0 0.1em;
+ line-height:100%;
+}
+
+
+/* FRAGMENTED INPUTS (DATE/TIME/DATETIME)
+--------------------------------------------------------------------------------------------------*/
+.formtastic .fragments {
+ position:relative;
+}
+
+.formtastic .fragments-group {
+ float:left;
+ width:74%;
+ margin:0;
+ padding:0 0 0 25%;
+}
+
+.formtastic .fragment {
+ float:left;
+ width:auto;
+ margin:0 .3em 0 0;
+ padding:0;
+ border:0;
+}
+
+.formtastic .fragment label {
+ display:none;
+}
+
+.formtastic .fragment label input {
+ display:inline;
+ margin:0;
+ padding:0;
+}
+
diff --git a/app/assets/stylesheets/formtastic_ie6.css b/app/assets/stylesheets/formtastic_ie6.css
new file mode 100644
index 000000000..b864ed415
--- /dev/null
+++ b/app/assets/stylesheets/formtastic_ie6.css
@@ -0,0 +1,33 @@
+/* additional stylesheets only for IE6, if you wish to support it */
+
+/* legend labels apper to have a left margin or padding I couldn't get rid of cleanly in main stylesheet */
+.formtastic .input fieldset legend.label {
+ margin-left:-7px;
+}
+
+/* checkbox and radio inputs appear to have a margin around them that I couldn't remove in main stylesheet */
+.formtastic .choice label input,
+.formtastic .boolean label input {
+ position:relative;
+ left:-1px;
+ size:15px;
+ margin-left:0;
+ margin-right:0;
+}
+
+/* inline hints and errors appear a few pixel too far over to the left */
+.formtastic .inline-hints,
+.formtastic .inline-errors {
+ padding-left:3px;
+}
+
+/* fragment (eg year, month, day) appear a few pixels too far to the left*/
+.formtastic .fragment {
+ padding-left:3px;
+}
+
+.formtastic .buttons,
+.formtastic .inputs,
+.formtastic .input {
+ zoom:1;
+}
diff --git a/app/assets/stylesheets/formtastic_ie7.css b/app/assets/stylesheets/formtastic_ie7.css
new file mode 100644
index 000000000..694c942af
--- /dev/null
+++ b/app/assets/stylesheets/formtastic_ie7.css
@@ -0,0 +1,23 @@
+/* additional stylesheets only for IE7, if you wish to support it */
+
+/* legend labels apper to have a left margin or padding I couldn't get rid of cleanly in main stylesheet */
+.formtastic .input fieldset legend.label {
+ margin-left:-7px;
+}
+
+/* checkbox and radio inputs appear to have a margin around them that I couldn't remove in main stylesheet */
+.formtastic .choice label input,
+.formtastic .boolean label input {
+ position:relative;
+ left:-4px;
+ right:-4px;
+ size:15px;
+ margin-left:0;
+ margin-right:0;
+}
+
+.formtastic .buttons,
+.formtastic .inputs,
+.formtastic .input {
+ zoom:1;
+}
diff --git a/formtastic.gemspec b/formtastic.gemspec
index 283747bd7..107e85edd 100644
--- a/formtastic.gemspec
+++ b/formtastic.gemspec
@@ -1,148 +1,37 @@
-# Generated by jeweler
-# DO NOT EDIT THIS FILE DIRECTLY
-# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
-# -*- encoding: utf-8 -*-
+# encoding: utf-8
+$:.push File.expand_path("../lib", __FILE__)
+require "formtastic/version"
Gem::Specification.new do |s|
- s.name = %q{formtastic}
- s.version = "0.9.8"
-
- s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
- s.authors = ["Justin French"]
- s.date = %q{2010-03-31}
+ s.name = %q{formtastic}
+ s.version = Formtastic::VERSION
+ s.platform = Gem::Platform::RUBY
+ s.authors = [%q{Justin French}]
+ s.email = [%q{justin@indent.com.au}]
+ s.homepage = %q{http://github.com/justinfrench/formtastic}
+ s.summary = %q{A Rails form builder plugin/gem with semantically rich and accessible markup}
s.description = %q{A Rails form builder plugin/gem with semantically rich and accessible markup}
- s.email = %q{justin@indent.com.au}
- s.extra_rdoc_files = [
- "README.textile"
- ]
- s.files = [
- "MIT-LICENSE",
- "README.textile",
- "Rakefile",
- "generators/form/USAGE",
- "generators/form/form_generator.rb",
- "generators/form/templates/view__form.html.erb",
- "generators/form/templates/view__form.html.haml",
- "generators/formtastic/formtastic_generator.rb",
- "generators/formtastic/templates/formtastic.css",
- "generators/formtastic/templates/formtastic.rb",
- "generators/formtastic/templates/formtastic_changes.css",
- "generators/formtastic_stylesheets/formtastic_stylesheets_generator.rb",
- "lib/formtastic.rb",
- "lib/formtastic/i18n.rb",
- "lib/formtastic/layout_helper.rb",
- "lib/locale/en.yml",
- "rails/init.rb",
- "spec/buttons_spec.rb",
- "spec/commit_button_spec.rb",
- "spec/custom_builder_spec.rb",
- "spec/custom_macros.rb",
- "spec/defaults_spec.rb",
- "spec/error_proc_spec.rb",
- "spec/errors_spec.rb",
- "spec/form_helper_spec.rb",
- "spec/i18n_spec.rb",
- "spec/include_blank_spec.rb",
- "spec/input_spec.rb",
- "spec/inputs/boolean_input_spec.rb",
- "spec/inputs/check_boxes_input_spec.rb",
- "spec/inputs/country_input_spec.rb",
- "spec/inputs/date_input_spec.rb",
- "spec/inputs/datetime_input_spec.rb",
- "spec/inputs/file_input_spec.rb",
- "spec/inputs/hidden_input_spec.rb",
- "spec/inputs/numeric_input_spec.rb",
- "spec/inputs/password_input_spec.rb",
- "spec/inputs/radio_input_spec.rb",
- "spec/inputs/select_input_spec.rb",
- "spec/inputs/string_input_spec.rb",
- "spec/inputs/text_input_spec.rb",
- "spec/inputs/time_input_spec.rb",
- "spec/inputs/time_zone_input_spec.rb",
- "spec/inputs_spec.rb",
- "spec/label_spec.rb",
- "spec/layout_helper_spec.rb",
- "spec/semantic_errors_spec.rb",
- "spec/semantic_fields_for_spec.rb",
- "spec/spec.opts",
- "spec/spec_helper.rb"
- ]
- s.homepage = %q{http://github.com/justinfrench/formtastic/tree/master}
- s.post_install_message = %q{
- ========================================================================
- Thanks for installing Formtastic!
- ------------------------------------------------------------------------
- You can now (optionally) run the generator to copy some stylesheets and
- a config initializer into your application:
- ./script/generate formtastic
- To generate some semantic form markup for your existing models, just run:
- ./script/generate form MODEL_NAME
+ s.files = `git ls-files`.split("\n")
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
+ s.require_paths = ["lib"]
- Find out more and get involved:
- http://github.com/justinfrench/formtastic
- http://groups.google.com.au/group/formtastic
- ========================================================================
- }
s.rdoc_options = ["--charset=UTF-8"]
- s.require_paths = ["lib"]
+ s.extra_rdoc_files = ["README.textile"]
+
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.rubygems_version = %q{1.3.6}
- s.summary = %q{A Rails form builder plugin/gem with semantically rich and accessible markup}
- s.test_files = [
- "spec/buttons_spec.rb",
- "spec/commit_button_spec.rb",
- "spec/custom_builder_spec.rb",
- "spec/custom_macros.rb",
- "spec/defaults_spec.rb",
- "spec/error_proc_spec.rb",
- "spec/errors_spec.rb",
- "spec/form_helper_spec.rb",
- "spec/i18n_spec.rb",
- "spec/include_blank_spec.rb",
- "spec/input_spec.rb",
- "spec/inputs/boolean_input_spec.rb",
- "spec/inputs/check_boxes_input_spec.rb",
- "spec/inputs/country_input_spec.rb",
- "spec/inputs/date_input_spec.rb",
- "spec/inputs/datetime_input_spec.rb",
- "spec/inputs/file_input_spec.rb",
- "spec/inputs/hidden_input_spec.rb",
- "spec/inputs/numeric_input_spec.rb",
- "spec/inputs/password_input_spec.rb",
- "spec/inputs/radio_input_spec.rb",
- "spec/inputs/select_input_spec.rb",
- "spec/inputs/string_input_spec.rb",
- "spec/inputs/text_input_spec.rb",
- "spec/inputs/time_input_spec.rb",
- "spec/inputs/time_zone_input_spec.rb",
- "spec/inputs_spec.rb",
- "spec/label_spec.rb",
- "spec/layout_helper_spec.rb",
- "spec/semantic_errors_spec.rb",
- "spec/semantic_fields_for_spec.rb",
- "spec/spec_helper.rb"
- ]
- if s.respond_to? :specification_version then
- current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
- s.specification_version = 3
+ s.add_dependency(%q, ["~> 3.0"])
- if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
- s.add_runtime_dependency(%q, [">= 2.3.0"])
- s.add_runtime_dependency(%q, [">= 2.3.0"])
- s.add_development_dependency(%q, [">= 1.2.6"])
- s.add_development_dependency(%q, [">= 1.0.0"])
- else
- s.add_dependency(%q, [">= 2.3.0"])
- s.add_dependency(%q, [">= 2.3.0"])
- s.add_dependency(%q, [">= 1.2.6"])
- s.add_dependency(%q, [">= 1.0.0"])
- end
- else
- s.add_dependency(%q, [">= 2.3.0"])
- s.add_dependency(%q, [">= 2.3.0"])
- s.add_dependency(%q, [">= 1.2.6"])
- s.add_dependency(%q, [">= 1.0.0"])
- end
+ s.add_development_dependency(%q, ["~> 2.5"])
+ s.add_development_dependency(%q, [">= 1.0.0"])
+ s.add_development_dependency(%q, ["~> 0.8.3"])
+ s.add_development_dependency(%q) # for YARD
+ s.add_development_dependency(%q, ["~> 0.6"])
+ s.add_development_dependency(%q, ["~> 0.9.9"])
+ s.add_development_dependency(%q)
+ s.add_development_dependency(%q)
+ s.add_development_dependency(%q, ["~> 0.1.2"])
end
-
diff --git a/generators/form/USAGE b/generators/form/USAGE
deleted file mode 100644
index 09e6bb4a0..000000000
--- a/generators/form/USAGE
+++ /dev/null
@@ -1,16 +0,0 @@
-NAME
- form - Formtastic form generator.
-
-DESCRIPTION
- Generates formtastic form code based on an existing model. By default the generated code will be printed out directly in the terminal, and also copied to clipboard. Can optionally be saved into partial directly.
-
- Required:
- ExistingModelName - The name of an existing model for which the generator should generate form code.
-
- Options:
- --haml Generate HAML instead of ERB.
- --partial Generate a form partial in the model views path, i.e. "_form.html.erb" or _form.html.haml".
- --controller PATH Generate for custom controller/view path - in case model and controller namespace is different, i.e. "admin/posts".
-
-EXAMPLE
- ./script/generate form ExistingModelName [--haml] [--partial]
\ No newline at end of file
diff --git a/generators/form/form_generator.rb b/generators/form/form_generator.rb
deleted file mode 100644
index 96ba3ce55..000000000
--- a/generators/form/form_generator.rb
+++ /dev/null
@@ -1,120 +0,0 @@
-# coding: utf-8
-# Get current OS - needed for clipboard functionality
-case RUBY_PLATFORM
-when /darwin/ then
- CURRENT_OS = :osx
-when /win32/
- CURRENT_OS = :win
- begin
- require 'win32/clipboard'
- rescue LoadError
- # Do nothing
- end
-else
- CURRENT_OS = :x
-end
-
-class FormGenerator < Rails::Generator::NamedBase
-
- default_options :haml => false,
- :partial => false
-
- VIEWS_PATH = File.join('app', 'views').freeze
- IGNORED_COLUMNS = [:updated_at, :created_at].freeze
-
- attr_reader :controller_file_name,
- :controller_class_path,
- :controller_class_nesting,
- :controller_class_nesting_depth,
- :controller_class_name,
- :template_type
-
- def initialize(runtime_args, runtime_options = {})
- super
- base_name, @controller_class_path = extract_modules(@name.pluralize)
- controller_class_name_without_nesting, @controller_file_name = inflect_names(base_name)
- @template_type = options[:haml] ? :haml : :erb
- end
-
- def manifest
- record do |m|
- if options[:partial]
- controller_and_view_path = options[:controller] || File.join(controller_class_path, controller_file_name)
- # Ensure directory exists.
- m.directory File.join(VIEWS_PATH, controller_and_view_path)
- # Create a form partial for the model as "_form" in it's views path.
- m.template "view__form.html.#{template_type}", File.join(VIEWS_PATH, controller_and_view_path, "_form.html.#{template_type}")
- else
- # Load template file, and render without saving to file
- template = File.read(File.join(source_root, "view__form.html.#{template_type}"))
- erb = ERB.new(template, nil, '-')
- generated_code = erb.result(binding).strip rescue nil
-
- # Print the result, and copy to clipboard
- puts "# ---------------------------------------------------------"
- puts "# GENERATED FORMTASTIC CODE"
- puts "# ---------------------------------------------------------"
- puts
- puts generated_code || " Nothing could be generated - model exists?"
- puts
- puts "# ---------------------------------------------------------"
- puts " Copied to clipboard - just paste it!" if save_to_clipboard(generated_code)
- end
- end
- end
-
- protected
-
- # Save to lipboard with multiple OS support.
- def save_to_clipboard(data)
- return unless data
- begin
- case CURRENT_OS
- when :osx
- `echo "#{data}" | pbcopy`
- when :win
- ::Win32::Clipboard.data = data
- else # :linux/:unix
- `echo "#{data}" | xsel --clipboard` || `echo "#{data}" | xclip`
- end
- rescue
- false
- else
- true
- end
- end
-
- # Add additional model attributes if specified in args - probably not that common scenario.
- def attributes
- # Get columns for the requested model.
- existing_attributes = @class_name.constantize.content_columns.reject { |column| IGNORED_COLUMNS.include?(column.name.to_sym) }
- @args = super + existing_attributes
- end
-
- def add_options!(opt)
- opt.separator ''
- opt.separator 'Options:'
-
- # Allow option to generate HAML views instead of ERB.
- opt.on('--haml',
- "Generate HAML output instead of the default ERB.") do |v|
- options[:haml] = v
- end
-
- # Allow option to generate to partial in model's views path, instead of printing out in terminal.
- opt.on('--partial',
- "Save generated output directly to a form partial (app/views/{resource}/_form.html.*).") do |v|
- options[:partial] = v
- end
-
- opt.on('--controller CONTROLLER_PATH',
- "Specify a non-standard controller for the specified model (e.g. admin/posts).") do |v|
- options[:controller] = v if v.present?
- end
- end
-
- def banner
- "Usage: #{$0} form ExistingModelName [--haml] [--partial]"
- end
-
-end
\ No newline at end of file
diff --git a/generators/form/templates/view__form.html.erb b/generators/form/templates/view__form.html.erb
deleted file mode 100644
index 21daaabb5..000000000
--- a/generators/form/templates/view__form.html.erb
+++ /dev/null
@@ -1,5 +0,0 @@
-<%% f.inputs do %>
-<% attributes.each do |attribute| -%>
- <%%= f.input :<%= attribute.name %>, :label => '<%= attribute.name.humanize %>' %>
-<% end -%>
-<%% end %>
\ No newline at end of file
diff --git a/generators/form/templates/view__form.html.haml b/generators/form/templates/view__form.html.haml
deleted file mode 100644
index 5e06093a8..000000000
--- a/generators/form/templates/view__form.html.haml
+++ /dev/null
@@ -1,4 +0,0 @@
-- f.inputs do
-<% attributes.each do |attribute| -%>
- = f.input :<%= attribute.name %>, :label => '<%= attribute.name.humanize %>'
-<% end -%>
\ No newline at end of file
diff --git a/generators/formtastic/formtastic_generator.rb b/generators/formtastic/formtastic_generator.rb
deleted file mode 100644
index 43d69eb3f..000000000
--- a/generators/formtastic/formtastic_generator.rb
+++ /dev/null
@@ -1,24 +0,0 @@
-class FormtasticGenerator < Rails::Generator::Base
-
- def initialize(*runtime_args)
- super
- end
-
- def manifest
- record do |m|
- m.directory File.join('config', 'initializers')
- m.template 'formtastic.rb', File.join('config', 'initializers', 'formtastic.rb')
-
- m.directory File.join('public', 'stylesheets')
- m.template 'formtastic.css', File.join('public', 'stylesheets', 'formtastic.css')
- m.template 'formtastic_changes.css', File.join('public', 'stylesheets', 'formtastic_changes.css')
- end
- end
-
- protected
-
- def banner
- %{Usage: #{$0} #{spec.name}\nCopies formtastic.css and formtastic_changes.css to public/stylesheets/ and a config initializer to config/initializers/formtastic.rb}
- end
-
-end
\ No newline at end of file
diff --git a/generators/formtastic/templates/formtastic.css b/generators/formtastic/templates/formtastic.css
deleted file mode 100644
index a64a16451..000000000
--- a/generators/formtastic/templates/formtastic.css
+++ /dev/null
@@ -1,146 +0,0 @@
-/* -------------------------------------------------------------------------------------------------
-
-It's *strongly* suggested that you don't modify this file. Instead, load a new stylesheet after
-this one in your layouts (eg formtastic_changes.css) and override the styles to suit your needs.
-This will allow you to update formtastic.css with new releases without clobbering your own changes.
-
-This stylesheet forms part of the Formtastic Rails Plugin
-(c) 2008 Justin French
-
---------------------------------------------------------------------------------------------------*/
-
-
-/* NORMALIZE AND RESET - obviously inspired by Yahoo's reset.css, but scoped to just form.formtastic
---------------------------------------------------------------------------------------------------*/
-form.formtastic, form.formtastic ul, form.formtastic ol, form.formtastic li, form.formtastic fieldset, form.formtastic legend, form.formtastic input, form.formtastic textarea, form.formtastic select, form.formtastic p { margin:0; padding:0; }
-form.formtastic fieldset { border:0; }
-form.formtastic em, form.formtastic strong { font-style:normal; font-weight:normal; }
-form.formtastic ol, form.formtastic ul { list-style:none; }
-form.formtastic abbr, form.formtastic acronym { border:0; font-variant:normal; }
-form.formtastic input, form.formtastic textarea, form.formtastic select { font-family:inherit; font-size:inherit; font-weight:inherit; }
-form.formtastic input, form.formtastic textarea, form.formtastic select { font-size:100%; }
-form.formtastic legend { white-space:normal; color:#000; }
-
-
-/* SEMANTIC ERRORS
---------------------------------------------------------------------------------------------------*/
-form.formtastic ul.errors { color:#cc0000; margin:0.5em 0 1.5em 25%; list-style:square; }
-form.formtastic ul.errors li { padding:0; border:none; display:list-item; }
-
-
-/* FIELDSETS & LISTS
---------------------------------------------------------------------------------------------------*/
-form.formtastic fieldset { }
-form.formtastic fieldset.inputs { }
-form.formtastic fieldset.buttons { padding-left:25%; }
-form.formtastic fieldset ol { }
-form.formtastic fieldset.buttons li { float:left; padding-right:0.5em; }
-
-/* clearfixing the fieldsets */
-form.formtastic fieldset { display: inline-block; }
-form.formtastic fieldset:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; }
-html[xmlns] form.formtastic fieldset { display: block; }
-* html form.formtastic fieldset { height: 1%; }
-
-
-/* INPUT LIs
---------------------------------------------------------------------------------------------------*/
-form.formtastic fieldset > ol > li { margin-bottom:1.5em; }
-
-/* clearfixing the li's */
-form.formtastic fieldset > ol > li { display: inline-block; }
-form.formtastic fieldset > ol > li:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; }
-html[xmlns] form.formtastic fieldset > ol > li { display: block; }
-* html form.formtastic fieldset > ol > li { height: 1%; }
-
-form.formtastic fieldset > ol > li.required { }
-form.formtastic fieldset > ol > li.optional { }
-form.formtastic fieldset > ol > li.error { }
-
-
-/* LABELS
---------------------------------------------------------------------------------------------------*/
-form.formtastic fieldset > ol > li label { display:block; width:25%; float:left; padding-top:.2em; }
-form.formtastic fieldset > ol > li > li label { line-height:100%; padding-top:0; }
-form.formtastic fieldset > ol > li > li label input { line-height:100%; vertical-align:middle; margin-top:-0.1em;}
-
-
-/* NESTED FIELDSETS AND LEGENDS (radio, check boxes and date/time inputs use nested fieldsets)
---------------------------------------------------------------------------------------------------*/
-form.formtastic fieldset > ol > li fieldset { position:relative; }
-form.formtastic fieldset > ol > li fieldset legend { position:absolute; width:95%; padding-top:0.1em; left: 0px; }
-form.formtastic fieldset > ol > li fieldset legend span { position:absolute; }
-form.formtastic fieldset > ol > li fieldset legend.label label { position:absolute; }
-form.formtastic fieldset > ol > li fieldset ol { float:left; width:74%; margin:0; padding:0 0 0 25%; }
-form.formtastic fieldset > ol > li fieldset ol li { padding:0; border:0; }
-
-
-/* INLINE HINTS
---------------------------------------------------------------------------------------------------*/
-form.formtastic fieldset > ol > li p.inline-hints { color:#666; margin:0.5em 0 0 25%; }
-
-
-/* INLINE ERRORS
---------------------------------------------------------------------------------------------------*/
-form.formtastic fieldset > ol > li p.inline-errors { color:#cc0000; margin:0.5em 0 0 25%; }
-form.formtastic fieldset > ol > li ul.errors { color:#cc0000; margin:0.5em 0 0 25%; list-style:square; }
-form.formtastic fieldset > ol > li ul.errors li { padding:0; border:none; display:list-item; }
-
-
-/* STRING & NUMERIC OVERRIDES
---------------------------------------------------------------------------------------------------*/
-form.formtastic fieldset > ol > li.string input { max-width:74%; }
-form.formtastic fieldset > ol > li.password input { max-width: 13em; }
-form.formtastic fieldset > ol > li.numeric input { max-width:74%; }
-
-
-/* TEXTAREA OVERRIDES
---------------------------------------------------------------------------------------------------*/
-form.formtastic fieldset > ol > li.text textarea { width:74%; }
-
-
-/* HIDDEN OVERRIDES
-The dual declarations are required because of our clearfix display hack on the LIs, which is more
-specific than the more general rule below. TODO: Revist the clearing hack and this rule.
---------------------------------------------------------------------------------------------------*/
-form.formtastic fieldset ol li.hidden,
-html[xmlns] form.formtastic fieldset ol li.hidden { display:none; }
-
-/* BOOLEAN OVERRIDES
---------------------------------------------------------------------------------------------------*/
-form.formtastic fieldset > ol > li.boolean label { padding-left:25%; width:auto; }
-form.formtastic fieldset > ol > li.boolean label input { margin:0 0.5em 0 0.2em; }
-
-
-/* RADIO OVERRIDES
---------------------------------------------------------------------------------------------------*/
-form.formtastic fieldset > ol > li.radio { }
-form.formtastic fieldset > ol > li.radio fieldset ol { margin-bottom:-0.6em; }
-form.formtastic fieldset > ol > li.radio fieldset ol li { margin:0.1em 0 0.5em 0; }
-form.formtastic fieldset > ol > li.radio fieldset ol li label { float:none; width:100%; }
-form.formtastic fieldset > ol > li.radio fieldset ol li label input { margin-right:0.2em; }
-
-
-/* CHECK BOXES (COLLECTION) OVERRIDES
---------------------------------------------------------------------------------------------------*/
-form.formtastic fieldset > ol > li.check_boxes { }
-form.formtastic fieldset > ol > li.check_boxes fieldset ol { margin-bottom:-0.6em; }
-form.formtastic fieldset > ol > li.check_boxes fieldset ol li { margin:0.1em 0 0.5em 0; }
-form.formtastic fieldset > ol > li.check_boxes fieldset ol li label { float:none; width:100%; }
-form.formtastic fieldset > ol > li.check_boxes fieldset ol li label input { margin-right:0.2em; }
-
-
-
-/* DATE & TIME OVERRIDES
---------------------------------------------------------------------------------------------------*/
-form.formtastic fieldset > ol > li.date fieldset ol li,
-form.formtastic fieldset > ol > li.time fieldset ol li,
-form.formtastic fieldset > ol > li.datetime fieldset ol li { float:left; width:auto; margin:0 .3em 0 0; }
-
-form.formtastic fieldset > ol > li.date fieldset ol li label,
-form.formtastic fieldset > ol > li.time fieldset ol li label,
-form.formtastic fieldset > ol > li.datetime fieldset ol li label { display:none; }
-
-form.formtastic fieldset > ol > li.date fieldset ol li label input,
-form.formtastic fieldset > ol > li.time fieldset ol li label input,
-form.formtastic fieldset > ol > li.datetime fieldset ol li label input { display:inline; margin:0; padding:0; }
diff --git a/generators/formtastic/templates/formtastic.rb b/generators/formtastic/templates/formtastic.rb
deleted file mode 100644
index ee76ff638..000000000
--- a/generators/formtastic/templates/formtastic.rb
+++ /dev/null
@@ -1,54 +0,0 @@
-# Set the default text field size when input is a string. Default is 50.
-# Formtastic::SemanticFormBuilder.default_text_field_size = 50
-
-# Set the default text area height when input is a text. Default is 20.
-# Formtastic::SemanticFormBuilder.default_text_area_height = 5
-
-# Should all fields be considered "required" by default?
-# Defaults to true, see ValidationReflection notes below.
-# Formtastic::SemanticFormBuilder.all_fields_required_by_default = true
-
-# Should select fields have a blank option/prompt by default?
-# Defaults to true.
-# Formtastic::SemanticFormBuilder.include_blank_for_select_by_default = true
-
-# Set the string that will be appended to the labels/fieldsets which are required
-# It accepts string or procs and the default is a localized version of
-# '* '. In other words, if you configure formtastic.required
-# in your locale, it will replace the abbr title properly. But if you don't want to use
-# abbr tag, you can simply give a string as below
-# Formtastic::SemanticFormBuilder.required_string = "(required)"
-
-# Set the string that will be appended to the labels/fieldsets which are optional
-# Defaults to an empty string ("") and also accepts procs (see required_string above)
-# Formtastic::SemanticFormBuilder.optional_string = "(optional)"
-
-# Set the way inline errors will be displayed.
-# Defaults to :sentence, valid options are :sentence, :list and :none
-# Formtastic::SemanticFormBuilder.inline_errors = :sentence
-
-# Set the method to call on label text to transform or format it for human-friendly
-# reading when formtastic is user without object. Defaults to :humanize.
-# Formtastic::SemanticFormBuilder.label_str_method = :humanize
-
-# Set the array of methods to try calling on parent objects in :select and :radio inputs
-# for the text inside each @@ tag or alongside each radio @ @. The first method
-# that is found on the object will be used.
-# Defaults to ["to_label", "display_name", "full_name", "name", "title", "username", "login", "value", "to_s"]
-# Formtastic::SemanticFormBuilder.collection_label_methods = [
-# "to_label", "display_name", "full_name", "name", "title", "username", "login", "value", "to_s"]
-
-# Formtastic by default renders inside li tags the input, hints and then
-# errors messages. Sometimes you want the hints to be rendered first than
-# the input, in the following order: hints, input and errors. You can
-# customize it doing just as below:
-# Formtastic::SemanticFormBuilder.inline_order = [:input, :hints, :errors]
-
-# Specifies if labels/hints for input fields automatically be looked up using I18n.
-# Default value: false. Overridden for specific fields by setting value to true,
-# i.e. :label => true, or :hint => true (or opposite depending on initialized value)
-# Formtastic::SemanticFormBuilder.i18n_lookups_by_default = false
-
-# You can add custom inputs or override parts of Formtastic by subclassing SemanticFormBuilder and
-# specifying that class here. Defaults to SemanticFormBuilder.
-# Formtastic::SemanticFormHelper.builder = MyCustomBuilder
diff --git a/generators/formtastic/templates/formtastic_changes.css b/generators/formtastic/templates/formtastic_changes.css
deleted file mode 100644
index 8a044fab6..000000000
--- a/generators/formtastic/templates/formtastic_changes.css
+++ /dev/null
@@ -1,14 +0,0 @@
-/* -------------------------------------------------------------------------------------------------
-
-Load this stylesheet after formtastic.css in your layouts to override the CSS to suit your needs.
-This will allow you to update formtastic.css with new releases without clobbering your own changes.
-
-For example, to make the inline hint paragraphs a little darker in color than the standard #666:
-
-form.formtastic fieldset > ol > li p.inline-hints { color:#333; }
-
-HINT:
-The following style may be *conditionally* included for improved support on older versions of IE(<8)
-form.formtastic fieldset ol li fieldset legend { margin-left: -6px;}
-
---------------------------------------------------------------------------------------------------*/
diff --git a/generators/formtastic_stylesheets/formtastic_stylesheets_generator.rb b/generators/formtastic_stylesheets/formtastic_stylesheets_generator.rb
deleted file mode 100644
index cf3255506..000000000
--- a/generators/formtastic_stylesheets/formtastic_stylesheets_generator.rb
+++ /dev/null
@@ -1,16 +0,0 @@
-class FormtasticStylesheetsGenerator < Rails::Generator::Base
-
- def initialize(*runtime_args)
- puts %q{
-===================================================
-Please run `./script/generate formtastic` instead.
-===================================================
- }
- end
-
- def manifest
- record do |m|
- end
- end
-
-end
\ No newline at end of file
diff --git a/install.rb b/install.rb
deleted file mode 100644
index 079f44702..000000000
--- a/install.rb
+++ /dev/null
@@ -1,2 +0,0 @@
-puts "Run `./script/generate formtastic` if you want (copies optional config file and some stylesheets into your app)"
-puts "Run `./script/generate form MODEL_NAME` to generate some semantic form markup for any existing model(s) (optional)"
\ No newline at end of file
diff --git a/lib/formtastic.rb b/lib/formtastic.rb
index ee0b0c81f..b116dcb96 100644
--- a/lib/formtastic.rb
+++ b/lib/formtastic.rb
@@ -1,1711 +1,25 @@
-# coding: utf-8
-require File.join(File.dirname(__FILE__), *%w[formtastic i18n])
-
-module Formtastic #:nodoc:
-
- class SemanticFormBuilder < ActionView::Helpers::FormBuilder
-
- @@default_text_field_size = 50
- @@default_text_area_height = 20
- @@all_fields_required_by_default = true
- @@include_blank_for_select_by_default = true
- @@required_string = proc { %{* } }
- @@optional_string = ''
- @@inline_errors = :sentence
- @@label_str_method = :humanize
- @@collection_label_methods = %w[to_label display_name full_name name title username login value to_s]
- @@inline_order = [ :input, :hints, :errors ]
- @@file_methods = [ :file?, :public_filename, :filename ]
- @@priority_countries = ["Australia", "Canada", "United Kingdom", "United States"]
- @@i18n_lookups_by_default = false
- @@default_commit_button_accesskey = nil
-
- cattr_accessor :default_text_field_size, :default_text_area_height, :all_fields_required_by_default, :include_blank_for_select_by_default,
- :required_string, :optional_string, :inline_errors, :label_str_method, :collection_label_methods,
- :inline_order, :file_methods, :priority_countries, :i18n_lookups_by_default, :default_commit_button_accesskey
-
- RESERVED_COLUMNS = [:created_at, :updated_at, :created_on, :updated_on, :lock_version, :version]
-
- INLINE_ERROR_TYPES = [:sentence, :list, :first]
-
- attr_accessor :template
-
- # Returns a suitable form input for the given +method+, using the database column information
- # and other factors (like the method name) to figure out what you probably want.
- #
- # Options:
- #
- # * :as - override the input type (eg force a :string to render as a :password field)
- # * :label - use something other than the method name as the label text, when false no label is printed
- # * :required - specify if the column is required (true) or not (false)
- # * :hint - provide some text to hint or help the user provide the correct information for a field
- # * :input_html - provide options that will be passed down to the generated input
- # * :wrapper_html - provide options that will be passed down to the li wrapper
- #
- # Input Types:
- #
- # Most inputs map directly to one of ActiveRecord's column types by default (eg string_input),
- # but there are a few special cases and some simplification (:integer, :float and :decimal
- # columns all map to a single numeric_input, for example).
- #
- # * :select (a select menu for associations) - default to association names
- # * :check_boxes (a set of check_box inputs for associations) - alternative to :select has_many and has_and_belongs_to_many associations
- # * :radio (a set of radio inputs for associations) - alternative to :select belongs_to associations
- # * :time_zone (a select menu with time zones)
- # * :password (a password input) - default for :string column types with 'password' in the method name
- # * :text (a textarea) - default for :text column types
- # * :date (a date select) - default for :date column types
- # * :datetime (a date and time select) - default for :datetime and :timestamp column types
- # * :time (a time select) - default for :time column types
- # * :boolean (a checkbox) - default for :boolean column types (you can also have booleans as :select and :radio)
- # * :string (a text field) - default for :string column types
- # * :numeric (a text field, like string) - default for :integer, :float and :decimal column types
- # * :country (a select menu of country names) - requires a country_select plugin to be installed
- # * :hidden (a hidden field) - creates a hidden field (added for compatibility)
- #
- # Example:
- #
- # <% semantic_form_for @employee do |form| %>
- # <% form.inputs do -%>
- # <%= form.input :secret, :value => "Hello" %>
- # <%= form.input :name, :label => "Full Name" %>
- # <%= form.input :manager_id, :as => :radio %>
- # <%= form.input :hired_at, :as => :date, :label => "Date Hired" %>
- # <%= form.input :phone, :required => false, :hint => "Eg: +1 555 1234" %>
- # <% end %>
- # <% end %>
- #
- def input(method, options = {})
- if options.key?(:selected) || options.key?(:checked) || options.key?(:default)
- ::ActiveSupport::Deprecation.warn(
- "The :selected, :checked (and :default) options are deprecated in Formtastic and will be removed from 1.0. " <<
- "Please set default values in your models (using an after_initialize callback) or in your controller set-up. " <<
- "See http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html for more information.", caller)
- end
-
- options[:required] = method_required?(method) unless options.key?(:required)
- options[:as] ||= default_input_type(method, options)
-
- html_class = [ options[:as], (options[:required] ? :required : :optional) ]
- html_class << 'error' if @object && @object.respond_to?(:errors) && !@object.errors[method.to_sym].blank?
-
- wrapper_html = options.delete(:wrapper_html) || {}
- wrapper_html[:id] ||= generate_html_id(method)
- wrapper_html[:class] = (html_class << wrapper_html[:class]).flatten.compact.join(' ')
-
- if options[:input_html] && options[:input_html][:id]
- options[:label_html] ||= {}
- options[:label_html][:for] ||= options[:input_html][:id]
- end
-
- input_parts = @@inline_order.dup
- input_parts = input_parts - [:errors, :hints] if options[:as] == :hidden
-
- list_item_content = input_parts.map do |type|
- send(:"inline_#{type}_for", method, options)
- end.compact.join("\n")
-
- return template.content_tag(:li, list_item_content, wrapper_html)
- end
-
- # Creates an input fieldset and ol tag wrapping for use around a set of inputs. It can be
- # called either with a block (in which you can do the usual Rails form stuff, HTML, ERB, etc),
- # or with a list of fields. These two examples are functionally equivalent:
- #
- # # With a block:
- # <% semantic_form_for @post do |form| %>
- # <% form.inputs do %>
- # <%= form.input :title %>
- # <%= form.input :body %>
- # <% end %>
- # <% end %>
- #
- # # With a list of fields:
- # <% semantic_form_for @post do |form| %>
- # <%= form.inputs :title, :body %>
- # <% end %>
- #
- # # Output:
- #
- #
- # === Quick Forms
- #
- # When called without a block or a field list, an input is rendered for each column in the
- # model's database table, just like Rails' scaffolding. You'll obviously want more control
- # than this in a production application, but it's a great way to get started, then come back
- # later to customise the form with a field list or a block of inputs. Example:
- #
- # <% semantic_form_for @post do |form| %>
- # <%= form.inputs %>
- # <% end %>
- #
- # With a few arguments:
- # <% semantic_form_for @post do |form| %>
- # <%= form.inputs "Post details", :title, :body %>
- # <% end %>
- #
- # === Options
- #
- # All options (with the exception of :name/:title) are passed down to the fieldset as HTML
- # attributes (id, class, style, etc). If provided, the :name/:title option is passed into a
- # legend tag inside the fieldset.
- #
- # # With a block:
- # <% semantic_form_for @post do |form| %>
- # <% form.inputs :name => "Create a new post", :style => "border:1px;" do %>
- # ...
- # <% end %>
- # <% end %>
- #
- # # With a list (the options must come after the field list):
- # <% semantic_form_for @post do |form| %>
- # <%= form.inputs :title, :body, :name => "Create a new post", :style => "border:1px;" %>
- # <% end %>
- #
- # # ...or the equivalent:
- # <% semantic_form_for @post do |form| %>
- # <%= form.inputs "Create a new post", :title, :body, :style => "border:1px;" %>
- # <% end %>
- #
- # === It's basically a fieldset!
- #
- # Instead of hard-coding fieldsets & legends into your form to logically group related fields,
- # use inputs:
- #
- # <% semantic_form_for @post do |f| %>
- # <% f.inputs do %>
- # <%= f.input :title %>
- # <%= f.input :body %>
- # <% end %>
- # <% f.inputs :name => "Advanced", :id => "advanced" do %>
- # <%= f.input :created_at %>
- # <%= f.input :user_id, :label => "Author" %>
- # <% end %>
- # <% f.inputs "Extra" do %>
- # <%= f.input :update_at %>
- # <% end %>
- # <% end %>
- #
- # # Output:
- #
- #
- # === Nested attributes
- #
- # As in Rails, you can use semantic_fields_for to nest attributes:
- #
- # <% semantic_form_for @post do |form| %>
- # <%= form.inputs :title, :body %>
- #
- # <% form.semantic_fields_for :author, @bob do |author_form| %>
- # <% author_form.inputs do %>
- # <%= author_form.input :first_name, :required => false %>
- # <%= author_form.input :last_name %>
- # <% end %>
- # <% end %>
- # <% end %>
- #
- # But this does not look formtastic! This is equivalent:
- #
- # <% semantic_form_for @post do |form| %>
- # <%= form.inputs :title, :body %>
- # <% form.inputs :for => [ :author, @bob ] do |author_form| %>
- # <%= author_form.input :first_name, :required => false %>
- # <%= author_form.input :last_name %>
- # <% end %>
- # <% end %>
- #
- # And if you don't need to give options to your input call, you could do it
- # in just one line:
- #
- # <% semantic_form_for @post do |form| %>
- # <%= form.inputs :title, :body %>
- # <%= form.inputs :first_name, :last_name, :for => @bob %>
- # <% end %>
- #
- # Just remember that calling inputs generates a new fieldset to wrap your
- # inputs. If you have two separate models, but, semantically, on the page
- # they are part of the same fieldset, you should use semantic_fields_for
- # instead (just as you would do with Rails' form builder).
- #
- def inputs(*args, &block)
- title = field_set_title_from_args(*args)
- html_options = args.extract_options!
- html_options[:class] ||= "inputs"
- html_options[:name] = title
-
- if html_options[:for] # Nested form
- inputs_for_nested_attributes(*(args << html_options), &block)
- elsif block_given?
- field_set_and_list_wrapping(*(args << html_options), &block)
- else
- if @object && args.empty?
- args = self.association_columns(:belongs_to)
- args += self.content_columns
- args -= RESERVED_COLUMNS
- args.compact!
- end
- legend = args.shift if args.first.is_a?(::String)
- contents = args.collect { |method| input(method.to_sym) }
- args.unshift(legend) if legend.present?
-
- field_set_and_list_wrapping(*((args << html_options) << contents))
- end
- end
- alias :input_field_set :inputs
-
- # Creates a fieldset and ol tag wrapping for form buttons / actions as list items.
- # See inputs documentation for a full example. The fieldset's default class attriute
- # is set to "buttons".
- #
- # See inputs for html attributes and special options.
- def buttons(*args, &block)
- html_options = args.extract_options!
- html_options[:class] ||= "buttons"
-
- if block_given?
- field_set_and_list_wrapping(html_options, &block)
- else
- args = [:commit] if args.empty?
- contents = args.map { |button_name| send(:"#{button_name}_button") }
- field_set_and_list_wrapping(html_options, contents)
- end
- end
- alias :button_field_set :buttons
-
- # Creates a submit input tag with the value "Save [model name]" (for existing records) or
- # "Create [model name]" (for new records) by default:
- #
- # <%= form.commit_button %> =>
- #
- # The value of the button text can be overridden:
- #
- # <%= form.commit_button "Go" %> =>
- # <%= form.commit_button :label => "Go" %> =>
- #
- # And you can pass html atributes down to the input, with or without the button text:
- #
- # <%= form.commit_button "Go" %> =>
- # <%= form.commit_button :class => "pretty" %> =>
- #
- def commit_button(*args)
- options = args.extract_options!
- text = options.delete(:label) || args.shift
-
- if @object && @object.respond_to?(:new_record?)
- key = @object.new_record? ? :create : :update
-
- # Deal with some complications with ActiveRecord::Base.human_name and two name models (eg UserPost)
- # ActiveRecord::Base.human_name falls back to ActiveRecord::Base.name.humanize ("Userpost")
- # if there's no i18n, which is pretty crappy. In this circumstance we want to detect this
- # fall back (human_name == name.humanize) and do our own thing name.underscore.humanize ("User Post")
- object_human_name = @object.class.human_name # default is UserPost => "Userpost", but i18n may do better ("User post")
- crappy_human_name = @object.class.name.humanize # UserPost => "Userpost"
- decent_human_name = @object.class.name.underscore.humanize # UserPost => "User post"
- object_name = (object_human_name == crappy_human_name) ? decent_human_name : object_human_name
- else
- key = :submit
- object_name = @object_name.to_s.send(@@label_str_method)
- end
-
- text = (self.localized_string(key, text, :action, :model => object_name) ||
- ::Formtastic::I18n.t(key, :model => object_name)) unless text.is_a?(::String)
-
- button_html = options.delete(:button_html) || {}
- button_html.merge!(:class => [button_html[:class], key].compact.join(' '))
- element_class = ['commit', options.delete(:class)].compact.join(' ') # TODO: Add class reflecting on form action.
- accesskey = (options.delete(:accesskey) || @@default_commit_button_accesskey) unless button_html.has_key?(:accesskey)
- button_html = button_html.merge(:accesskey => accesskey) if accesskey
- template.content_tag(:li, self.submit(text, button_html), :class => element_class)
- end
-
- # A thin wrapper around #fields_for to set :builder => Formtastic::SemanticFormBuilder
- # for nesting forms:
- #
- # # Example:
- # <% semantic_form_for @post do |post| %>
- # <% post.semantic_fields_for :author do |author| %>
- # <% author.inputs :name %>
- # <% end %>
- # <% end %>
- #
- # # Output:
- #
- #
- def semantic_fields_for(record_or_name_or_array, *args, &block)
- opts = args.extract_options!
- opts[:builder] ||= Formtastic::SemanticFormHelper.builder
- args.push(opts)
- fields_for(record_or_name_or_array, *args, &block)
- end
-
- # Generates the label for the input. It also accepts the same arguments as
- # Rails label method. It has three options that are not supported by Rails
- # label method:
- #
- # * :required - Appends an abbr tag if :required is true
- # * :label - An alternative form to give the label content. Whenever label
- # is false, a blank string is returned.
- # * :input_name - Gives the input to match for. This is needed when you want to
- # to call f.label :authors but it should match :author_ids.
- #
- # == Examples
- #
- # f.label :title # like in rails, except that it searches the label on I18n API too
- #
- # f.label :title, "Your post title"
- # f.label :title, :label => "Your post title" # Added for formtastic API
- #
- # f.label :title, :required => true # Returns Title*
- #
- def label(method, options_or_text=nil, options=nil)
- if options_or_text.is_a?(Hash)
- return "" if options_or_text[:label] == false
- options = options_or_text
- text = options.delete(:label)
- else
- text = options_or_text
- options ||= {}
- end
- text = localized_string(method, text, :label) || humanized_attribute_name(method)
- text += required_or_optional_string(options.delete(:required))
-
- # special case for boolean (checkbox) labels, which have a nested input
- text = (options.delete(:label_prefix_for_nested_input) || "") + text
-
- input_name = options.delete(:input_name) || method
- super(input_name, text, options)
- end
-
- # Generates error messages for the given method. Errors can be shown as list,
- # as sentence or just the first error can be displayed. If :none is set, no error is shown.
- #
- # This method is also aliased as errors_on, so you can call on your custom
- # inputs as well:
- #
- # semantic_form_for :post do |f|
- # f.text_field(:body)
- # f.errors_on(:body)
- # end
- #
- def inline_errors_for(method, options = nil) #:nodoc:
- if render_inline_errors?
- errors = @object.errors[method.to_sym]
- send(:"error_#{@@inline_errors}", [*errors]) if errors.present?
- else
- nil
- end
- end
- alias :errors_on :inline_errors_for
-
- # Generates error messages for given method names and for base.
- # You can pass a hash with html options that will be added to ul tag
- #
- # == Examples
- #
- # f.semantic_errors # This will show only errors on base
- # f.semantic_errors :state # This will show errors on base and state
- # f.semantic_errors :state, :class => "awesome" # errors will be rendered in ul.awesome
- #
- def semantic_errors(*args)
- html_options = args.extract_options!
- full_errors = args.inject([]) do |array, method|
- attribute = localized_string(method, method.to_sym, :label) || humanized_attribute_name(method)
- errors = Array(@object.errors[method.to_sym]).to_sentence
- errors.present? ? array << [attribute, errors].join(" ") : array ||= []
- end
- full_errors << @object.errors.on_base
- full_errors.flatten!
- full_errors.compact!
- return nil if full_errors.blank?
- html_options[:class] ||= "errors"
- template.content_tag(:ul, html_options) do
- full_errors.map { |error| template.content_tag(:li, error) }.join
- end
- end
-
- protected
-
- def render_inline_errors?
- @object && @object.respond_to?(:errors) && INLINE_ERROR_TYPES.include?(@@inline_errors)
- end
-
- # Collects content columns (non-relation columns) for the current form object class.
- #
- def content_columns #:nodoc:
- self.model_name.constantize.content_columns.collect { |c| c.name.to_sym }.compact rescue []
- end
-
- # Collects association columns (relation columns) for the current form object class.
- #
- def association_columns(*by_associations) #:nodoc:
- if @object.present?
- @object.class.reflections.collect do |name, _|
- if by_associations.present?
- name if by_associations.include?(_.macro)
- else
- name
- end
- end.compact
- else
- []
- end
- end
-
- # Prepare options to be sent to label
- #
- def options_for_label(options) #:nodoc:
- options.slice(:label, :required).merge!(options.fetch(:label_html, {}))
- end
-
- # Deals with :for option when it's supplied to inputs methods. Additional
- # options to be passed down to :for should be supplied using :for_options
- # key.
- #
- # It should raise an error if a block with arity zero is given.
- #
- def inputs_for_nested_attributes(*args, &block) #:nodoc:
- options = args.extract_options!
- args << options.merge!(:parent => { :builder => self, :for => options[:for] })
-
- fields_for_block = if block_given?
- raise ArgumentError, 'You gave :for option with a block to inputs method, ' <<
- 'but the block does not accept any argument.' if block.arity <= 0
-
- proc { |f| f.inputs(*args){ block.call(f) } }
- else
- proc { |f| f.inputs(*args) }
- end
-
- fields_for_args = [options.delete(:for), options.delete(:for_options) || {}].flatten
- semantic_fields_for(*fields_for_args, &fields_for_block)
- end
-
- # Remove any Formtastic-specific options before passing the down options.
- #
- def strip_formtastic_options(options) #:nodoc:
- options.except(:value_method, :label_method, :collection, :required, :label,
- :as, :hint, :input_html, :label_html, :value_as_class)
- end
-
- # Determins if the attribute (eg :title) should be considered required or not.
- #
- # * if the :required option was provided in the options hash, the true/false value will be
- # returned immediately, allowing the view to override any guesswork that follows:
- #
- # * if the :required option isn't provided in the options hash, and the ValidationReflection
- # plugin is installed (http://github.com/redinger/validation_reflection), true is returned
- # if the validates_presence_of macro has been used in the class for this attribute, or false
- # otherwise.
- #
- # * if the :required option isn't provided, and the plugin isn't available, the value of the
- # configuration option @@all_fields_required_by_default is used.
- #
- def method_required?(attribute) #:nodoc:
- if @object && @object.class.respond_to?(:reflect_on_validations_for)
- attribute_sym = attribute.to_s.sub(/_id$/, '').to_sym
-
- @object.class.reflect_on_validations_for(attribute_sym).any? do |validation|
- validation.macro == :validates_presence_of &&
- validation.name == attribute_sym &&
- (validation.options.present? ? options_require_validation?(validation.options) : true)
- end
- else
- @@all_fields_required_by_default
- end
- end
-
- # Determines whether the given options evaluate to true
- def options_require_validation?(options) #nodoc
- if_condition = !options[:if].nil?
- condition = if_condition ? options[:if] : options[:unless]
-
- condition = if condition.respond_to?(:call)
- condition.call(@object)
- elsif condition.is_a?(::Symbol) && @object.respond_to?(condition)
- @object.send(condition)
- else
- condition
- end
-
- if_condition ? !!condition : !condition
- end
-
- def basic_input_helper(form_helper_method, type, method, options) #:nodoc:
- html_options = options.delete(:input_html) || {}
- html_options = default_string_options(method, type).merge(html_options) if [:numeric, :string, :password, :text].include?(type)
-
- self.label(method, options_for_label(options)) <<
- self.send(form_helper_method, method, html_options)
- end
-
- # Outputs a label and standard Rails text field inside the wrapper.
- def string_input(method, options)
- basic_input_helper(:text_field, :string, method, options)
- end
-
- # Outputs a label and standard Rails password field inside the wrapper.
- def password_input(method, options)
- basic_input_helper(:password_field, :password, method, options)
- end
-
- # Outputs a label and standard Rails text field inside the wrapper.
- def numeric_input(method, options)
- basic_input_helper(:text_field, :numeric, method, options)
- end
-
- # Ouputs a label and standard Rails text area inside the wrapper.
- def text_input(method, options)
- basic_input_helper(:text_area, :text, method, options)
- end
-
- # Outputs a label and a standard Rails file field inside the wrapper.
- def file_input(method, options)
- basic_input_helper(:file_field, :file, method, options)
- end
-
- # Outputs a hidden field inside the wrapper, which should be hidden with CSS.
- # Additionals options can be given and will be sent straight to hidden input
- # element.
- #
- def hidden_input(method, options)
- options ||= {}
- if options[:input_html].present?
- options[:value] = options[:input_html][:value] if options[:input_html][:value].present?
- end
- self.hidden_field(method, strip_formtastic_options(options))
- end
-
- # Outputs a label and a select box containing options from the parent
- # (belongs_to, has_many, has_and_belongs_to_many) association. If an association
- # is has_many or has_and_belongs_to_many the select box will be set as multi-select
- # and size = 5
- #
- # Example (belongs_to):
- #
- # f.input :author
- #
- # Author
- #
- #
- # Justin French
- # Jane Doe
- #
- #
- # Example (has_many):
- #
- # f.input :chapters
- #
- # Chapters
- #
- #
- # Chapter 1
- # Chapter 2
- #
- #
- # Example (has_and_belongs_to_many):
- #
- # f.input :authors
- #
- # Authors
- #
- #
- # Justin French
- # Jane Doe
- #
- #
- #
- # You can customize the options available in the select by passing in a collection (an Array or
- # Hash) through the :collection option. If not provided, the choices are found by inferring the
- # parent's class name from the method name and simply calling find(:all) on it
- # (VehicleOwner.find(:all) in the example above).
- #
- # Examples:
- #
- # f.input :author, :collection => @authors
- # f.input :author, :collection => Author.find(:all)
- # f.input :author, :collection => [@justin, @kate]
- # f.input :author, :collection => {@justin.name => @justin.id, @kate.name => @kate.id}
- # f.input :author, :collection => ["Justin", "Kate", "Amelia", "Gus", "Meg"]
- #
- # The :label_method option allows you to customize the text label inside each option tag two ways:
- #
- # * by naming the correct method to call on each object in the collection as a symbol (:name, :login, etc)
- # * by passing a Proc that will be called on each object in the collection, allowing you to use helpers or multiple model attributes together
- #
- # Examples:
- #
- # f.input :author, :label_method => :full_name
- # f.input :author, :label_method => :login
- # f.input :author, :label_method => :full_name_with_post_count
- # f.input :author, :label_method => Proc.new { |a| "#{a.name} (#{pluralize("post", a.posts.count)})" }
- #
- # The :value_method option provides the same customization of the value attribute of each option tag.
- #
- # Examples:
- #
- # f.input :author, :value_method => :full_name
- # f.input :author, :value_method => :login
- # f.input :author, :value_method => Proc.new { |a| "author_#{a.login}" }
- #
- # You can pre-select a specific option value by passing in the :selected option.
- #
- # Examples:
- #
- # f.input :author, :selected => current_user.id
- # f.input :author, :value_method => :login, :selected => current_user.login
- # f.input :authors, :value_method => :login, :selected => Author.most_popular.collect(&:id)
- # f.input :authors, :value_method => :login, :selected => nil # override any defaults: select none
- #
- # You can pass html_options to the select tag using :input_html => {}
- #
- # Examples:
- #
- # f.input :authors, :input_html => {:size => 20, :multiple => true}
- #
- # By default, all select inputs will have a blank option at the top of the list. You can add
- # a prompt with the :prompt option, or disable the blank option with :include_blank => false.
- #
- #
- # You can group the options in optgroup elements by passing the :group_by option
- # (Note: only tested for belongs_to relations)
- #
- # Examples:
- #
- # f.input :author, :group_by => :continent
- #
- # All the other options should work as expected. If you want to call a custom method on the
- # group item. You can include the option:group_label_method
- # Examples:
- #
- # f.input :author, :group_by => :continents, :group_label_method => :something_different
- #
- def select_input(method, options)
- html_options = options.delete(:input_html) || {}
- options = set_include_blank(options)
- html_options[:multiple] = html_options[:multiple] || options.delete(:multiple)
- html_options.delete(:multiple) if html_options[:multiple].nil?
-
- reflection = self.reflection_for(method)
- if reflection && [ :has_many, :has_and_belongs_to_many ].include?(reflection.macro)
- options[:include_blank] = false
- html_options[:multiple] = true if html_options[:multiple].nil?
- html_options[:size] ||= 5
- end
- options[:selected] = options[:selected].first if options[:selected].present? && html_options[:multiple] == false
- input_name = generate_association_input_name(method)
-
- select_html = if options[:group_by]
- # The grouped_options_select is a bit counter intuitive and not optimised (mostly due to ActiveRecord).
- # The formtastic user however shouldn't notice this too much.
- raw_collection = find_raw_collection_for_column(method, options.reverse_merge(:find_options => { :include => options[:group_by] }))
- label, value = detect_label_and_value_method!(raw_collection)
- group_collection = raw_collection.map { |option| option.send(options[:group_by]) }.uniq
- group_label_method = options[:group_label_method] || detect_label_method(group_collection)
- group_collection = group_collection.sort_by { |group_item| group_item.send(group_label_method) }
- group_association = options[:group_association] || detect_group_association(method, options[:group_by])
-
- # Here comes the monster with 8 arguments
- self.grouped_collection_select(input_name, group_collection,
- group_association, group_label_method,
- value, label,
- strip_formtastic_options(options), html_options)
- else
- collection = find_collection_for_column(method, options)
-
- self.select(input_name, collection, strip_formtastic_options(options), html_options)
- end
-
- self.label(method, options_for_label(options).merge(:input_name => input_name)) << select_html
- end
- alias :boolean_select_input :select_input
-
- # Outputs a timezone select input as Rails' time_zone_select helper. You
- # can give priority zones as option.
- #
- # Examples:
- #
- # f.input :time_zone, :as => :time_zone, :priority_zones => /Australia/
- #
- # You can pre-select a specific option value by passing in the :selected option.
- # Note: Right now only works if the form object attribute value is not set (nil),
- # because of how the core helper is implemented.
- #
- # Examples:
- #
- # f.input :my_favorite_time_zone, :as => :time_zone, :selected => 'Singapore'
- #
- def time_zone_input(method, options)
- html_options = options.delete(:input_html) || {}
- selected_value = options.delete(:selected)
-
- self.label(method, options_for_label(options)) <<
- self.time_zone_select(method, options.delete(:priority_zones),
- strip_formtastic_options(options).merge(:default => selected_value), html_options)
- end
-
- # Outputs a fieldset containing a legend for the label text, and an ordered list (ol) of list
- # items, one for each possible choice in the belongs_to association. Each li contains a
- # label and a radio input.
- #
- # Example:
- #
- # f.input :author, :as => :radio
- #
- # Output:
- #
- #
- # Author
- #
- #
- # Justin French
- #
- #
- # Kate French
- #
- #
- #
- #
- # You can customize the choices available in the radio button set by passing in a collection (an Array or
- # Hash) through the :collection option. If not provided, the choices are found by reflecting on the association
- # (Author.find(:all) in the example above).
- #
- # Examples:
- #
- # f.input :author, :as => :radio, :collection => @authors
- # f.input :author, :as => :radio, :collection => Author.find(:all)
- # f.input :author, :as => :radio, :collection => [@justin, @kate]
- # f.input :author, :collection => ["Justin", "Kate", "Amelia", "Gus", "Meg"]
- #
- # The :label_method option allows you to customize the label for each radio button two ways:
- #
- # * by naming the correct method to call on each object in the collection as a symbol (:name, :login, etc)
- # * by passing a Proc that will be called on each object in the collection, allowing you to use helpers or multiple model attributes together
- #
- # Examples:
- #
- # f.input :author, :as => :radio, :label_method => :full_name
- # f.input :author, :as => :radio, :label_method => :login
- # f.input :author, :as => :radio, :label_method => :full_name_with_post_count
- # f.input :author, :as => :radio, :label_method => Proc.new { |a| "#{a.name} (#{pluralize("post", a.posts.count)})" }
- #
- # The :value_method option provides the same customization of the value attribute of each option tag.
- #
- # Examples:
- #
- # f.input :author, :as => :radio, :value_method => :full_name
- # f.input :author, :as => :radio, :value_method => :login
- # f.input :author, :as => :radio, :value_method => Proc.new { |a| "author_#{a.login}" }
- #
- # You can force a particular radio button in the collection to be checked with the :selected option.
- #
- # Examples:
- #
- # f.input :subscribe_to_newsletter, :as => :radio, :selected => true
- # f.input :subscribe_to_newsletter, :as => :radio, :collection => ["Yeah!", "Nope!"], :selected => "Nope!"
- #
- # Finally, you can set :value_as_class => true if you want the li wrapper around each radio
- # button / label combination to contain a class with the value of the radio button (useful for
- # applying specific CSS or Javascript to a particular radio button).
- #
- def radio_input(method, options)
- collection = find_collection_for_column(method, options)
- html_options = strip_formtastic_options(options).merge(options.delete(:input_html) || {})
-
- input_name = generate_association_input_name(method)
- value_as_class = options.delete(:value_as_class)
- input_ids = []
- selected_option_is_present = [:selected, :checked].any? { |k| options.key?(k) }
- selected_value = (options.key?(:checked) ? options[:checked] : options[:selected]) if selected_option_is_present
-
- list_item_content = collection.map do |c|
- label = c.is_a?(Array) ? c.first : c
- value = c.is_a?(Array) ? c.last : c
- input_id = generate_html_id(input_name, value.to_s.gsub(/\s/, '_').gsub(/\W/, '').downcase)
- input_ids << input_id
-
- html_options[:checked] = selected_value == value if selected_option_is_present
-
- li_content = template.content_tag(:label,
- "#{self.radio_button(input_name, value, html_options)} #{label}",
- :for => input_id
- )
-
- li_options = value_as_class ? { :class => [method.to_s.singularize, value.to_s.downcase].join('_') } : {}
- template.content_tag(:li, li_content, li_options)
- end
-
- field_set_and_list_wrapping_for_method(method, options, list_item_content)
- end
- alias :boolean_radio_input :radio_input
-
- # Outputs a fieldset with a legend for the method label, and a ordered list (ol) of list
- # items (li), one for each fragment for the date (year, month, day). Each li contains a label
- # (eg "Year") and a select box. Overwriting the label is possible by adding the :labels option.
- # :labels should be a hash with the field (e.g. day) as key and the label text as value.
- # See date_or_datetime_input for a more detailed output example.
- #
- # You can pre-select a specific option value by passing in the :selected option.
- #
- # Examples:
- #
- # f.input :created_at, :as => :date, :selected => 1.day.ago
- # f.input :created_at, :as => :date, :selected => nil # override any defaults: select none
- # f.input :created_at, :as => :date, :labels => { :year => "Year", :month => "Month", :day => "Day" }
- #
- # Some of Rails' options for select_date are supported, but not everything yet, see
- # documentation of date_or_datetime_input() for more information.
- def date_input(method, options)
- options = set_include_blank(options)
- date_or_datetime_input(method, options.merge(:discard_hour => true))
- end
-
- # Outputs a fieldset with a legend for the method label, and a ordered list (ol) of list
- # items (li), one for each fragment for the date (year, month, day, hour, min, sec). Each li
- # contains a label (eg "Year") and a select box. Overwriting the label is possible by adding
- # the :labels option. :labels should be a hash with the field (e.g. day) as key and the label
- # text as value. See date_or_datetime_input for a more detailed output example.
- #
- # You can pre-select a specific option value by passing in the :selected option.
- #
- # Examples:
- #
- # f.input :created_at, :as => :datetime, :selected => 1.day.ago
- # f.input :created_at, :as => :datetime, :selected => nil # override any defaults: select none
- # f.input :created_at, :as => :date, :labels => { :year => "Year", :month => "Month", :day => "Day",
- # :hour => "Hour", :minute => "Minute" }
- #
- # Some of Rails' options for select_date are supported, but not everything yet, see
- # documentation of date_or_datetime_input() for more information.
- def datetime_input(method, options)
- options = set_include_blank(options)
- date_or_datetime_input(method, options)
- end
-
- # Outputs a fieldset with a legend for the method label, and a ordered list (ol) of list
- # items (li), one for each fragment for the time (hour, minute, second). Each li contains a label
- # (eg "Hour") and a select box. Overwriting the label is possible by adding the :labels option.
- # :labels should be a hash with the field (e.g. day) as key and the label text as value.
- # See date_or_datetime_input for a more detailed output example.
- #
- # You can pre-select a specific option value by passing in the :selected option.
- #
- # Examples:
- #
- # f.input :created_at, :as => :time, :selected => 1.hour.ago
- # f.input :created_at, :as => :time, :selected => nil # override any defaults: select none
- # f.input :created_at, :as => :date, :labels => { :hour => "Hour", :minute => "Minute" }
- #
- # Some of Rails' options for select_time are supported, but not everything yet, see
- # documentation of date_or_datetime_input() for more information.
- def time_input(method, options)
- options = set_include_blank(options)
- date_or_datetime_input(method, options.merge(:discard_year => true, :discard_month => true, :discard_day => true))
- end
-
- # Helper method used by :as => (:date|:datetime|:time). Generates a fieldset containing a
- # legend (for what would normally be considered the label), and an ordered list of list items
- # for year, month, day, hour, etc, each containing a label and a select. Example:
- #
- #
- # Created At
- #
- #
- # Year
- #
- # 2003
- # ...
- # 2013
- #
- #
- #
- # Month
- #
- # January
- # ...
- # December
- #
- #
- #
- # Day
- #
- # 1
- # ...
- # 31
- #
- #
- #
- #
- #
- # This is an absolute abomination, but so is the official Rails select_date().
- #
- # Options:
- #
- # * @:order => [:month, :day, :year]@
- # * @:include_seconds@ => true@
- # * @:selected => Time.mktime(2008)@
- # * @:selected => Date.new(2008)@
- # * @:selected => nil@
- # * @:discard_(year|month|day|hour|minute) => true@
- # * @:include_blank => true@
- # * @:labels => {}@
- def date_or_datetime_input(method, options)
- position = { :year => 1, :month => 2, :day => 3, :hour => 4, :minute => 5, :second => 6 }
- i18n_date_order = ::I18n.t(:order, :scope => [:date])
- i18n_date_order = nil unless i18n_date_order.is_a?(Array)
- inputs = options.delete(:order) || i18n_date_order || [:year, :month, :day]
- labels = options.delete(:labels) || {}
-
- time_inputs = [:hour, :minute]
- time_inputs << :second if options[:include_seconds]
-
- list_items_capture = ""
- hidden_fields_capture = ""
-
- datetime = options.key?(:selected) ? options[:selected] : Time.now # can't do an || because nil is an important value
- datetime = @object.send(method) if @object && @object.send(method) # object trumps :selected
-
- html_options = options.delete(:input_html) || {}
- input_ids = []
-
- (inputs + time_inputs).each do |input|
- input_ids << input_id = generate_html_id(method, "#{position[input]}i")
-
- field_name = "#{method}(#{position[input]}i)"
- if options[:"discard_#{input}"]
- break if time_inputs.include?(input)
-
- hidden_value = datetime.respond_to?(input) ? datetime.send(input) : datetime
- hidden_fields_capture << template.hidden_field_tag("#{@object_name}[#{field_name}]", (hidden_value || 1), :id => input_id)
- else
- opts = strip_formtastic_options(options).merge(:prefix => @object_name, :field_name => field_name, :default => datetime)
- item_label_text = labels[input] || ::I18n.t(input.to_s, :default => input.to_s.humanize, :scope => [:datetime, :prompts])
-
- list_items_capture << template.content_tag(:li, [
- !item_label_text.blank? ? template.content_tag(:label, item_label_text, :for => input_id) : "",
- template.send(:"select_#{input}", datetime, opts, html_options.merge(:id => input_id))
- ].join("")
- )
- end
- end
-
- hidden_fields_capture << field_set_and_list_wrapping_for_method(method, options.merge(:label_for => input_ids.first), list_items_capture)
- end
-
- # Outputs a fieldset containing a legend for the label text, and an ordered list (ol) of list
- # items, one for each possible choice in the belongs_to association. Each li contains a
- # label and a check_box input.
- #
- # This is an alternative for has many and has and belongs to many associations.
- #
- # Example:
- #
- # f.input :author, :as => :check_boxes
- #
- # Output:
- #
- #
- # Authors
- #
- #
- #
- # Justin French
- #
- #
- #
- # Kate French
- #
- #
- #
- #
- # Notice that the value of the checkbox is the same as the id and the hidden
- # field has empty value. You can override the hidden field value using the
- # unchecked_value option.
- #
- # You can customize the options available in the set by passing in a collection (Array) of
- # ActiveRecord objects through the :collection option. If not provided, the choices are found
- # by inferring the parent's class name from the method name and simply calling find(:all) on
- # it (Author.find(:all) in the example above).
- #
- # Examples:
- #
- # f.input :author, :as => :check_boxes, :collection => @authors
- # f.input :author, :as => :check_boxes, :collection => Author.find(:all)
- # f.input :author, :as => :check_boxes, :collection => [@justin, @kate]
- #
- # The :label_method option allows you to customize the label for each checkbox two ways:
- #
- # * by naming the correct method to call on each object in the collection as a symbol (:name, :login, etc)
- # * by passing a Proc that will be called on each object in the collection, allowing you to use helpers or multiple model attributes together
- #
- # Examples:
- #
- # f.input :author, :as => :check_boxes, :label_method => :full_name
- # f.input :author, :as => :check_boxes, :label_method => :login
- # f.input :author, :as => :check_boxes, :label_method => :full_name_with_post_count
- # f.input :author, :as => :check_boxes, :label_method => Proc.new { |a| "#{a.name} (#{pluralize("post", a.posts.count)})" }
- #
- # The :value_method option provides the same customization of the value attribute of each checkbox input tag.
- #
- # Examples:
- #
- # f.input :author, :as => :check_boxes, :value_method => :full_name
- # f.input :author, :as => :check_boxes, :value_method => :login
- # f.input :author, :as => :check_boxes, :value_method => Proc.new { |a| "author_#{a.login}" }
- #
- # You can pre-select/check a specific checkbox value by passing in the :selected option (alias :checked works as well).
- #
- # Examples:
- #
- # f.input :authors, :as => :check_boxes, :selected => @justin
- # f.input :authors, :as => :check_boxes, :selected => Author.most_popular.collect(&:id)
- # f.input :authors, :as => :check_boxes, :selected => nil # override any defaults: select none
- #
- # Finally, you can set :value_as_class => true if you want the li wrapper around each checkbox / label
- # combination to contain a class with the value of the radio button (useful for applying specific
- # CSS or Javascript to a particular checkbox).
- #
- def check_boxes_input(method, options)
- collection = find_collection_for_column(method, options)
- html_options = options.delete(:input_html) || {}
-
- input_name = generate_association_input_name(method)
- value_as_class = options.delete(:value_as_class)
- unchecked_value = options.delete(:unchecked_value) || ''
- html_options = { :name => "#{@object_name}[#{input_name}][]" }.merge(html_options)
- input_ids = []
-
- selected_option_is_present = [:selected, :checked].any? { |k| options.key?(k) }
- selected_values = (options.key?(:checked) ? options[:checked] : options[:selected]) if selected_option_is_present
- selected_values = [*selected_values].compact
-
- list_item_content = collection.map do |c|
- label = c.is_a?(Array) ? c.first : c
- value = c.is_a?(Array) ? c.last : c
- input_id = generate_html_id(input_name, value.to_s.gsub(/\s/, '_').gsub(/\W/, '').downcase)
- input_ids << input_id
-
- html_options[:checked] = selected_values.include?(value) if selected_option_is_present
- html_options[:id] = input_id
-
- li_content = template.content_tag(:label,
- "#{self.check_box(input_name, html_options, value, unchecked_value)} #{label}",
- :for => input_id
- )
-
- li_options = value_as_class ? { :class => [method.to_s.singularize, value.to_s.downcase].join('_') } : {}
- template.content_tag(:li, li_content, li_options)
- end
-
- field_set_and_list_wrapping_for_method(method, options, list_item_content)
- end
-
- # Outputs a country select input, wrapping around a regular country_select helper.
- # Rails doesn't come with a country_select helper by default any more, so you'll need to install
- # the "official" plugin, or, if you wish, any other country_select plugin that behaves in the
- # same way.
- #
- # The Rails plugin iso-3166-country-select plugin can be found "here":http://github.com/rails/iso-3166-country-select.
- #
- # By default, Formtastic includes a handfull of english-speaking countries as "priority counties",
- # which you can change to suit your market and user base (see README for more info on config).
- #
- # Examples:
- # f.input :location, :as => :country # use Formtastic::SemanticFormBuilder.priority_countries array for the priority countries
- # f.input :location, :as => :country, :priority_countries => /Australia/ # set your own
- #
- def country_input(method, options)
- raise "To use the :country input, please install a country_select plugin, like this one: http://github.com/rails/iso-3166-country-select" unless self.respond_to?(:country_select)
-
- html_options = options.delete(:input_html) || {}
- priority_countries = options.delete(:priority_countries) || @@priority_countries
-
- self.label(method, options_for_label(options)) <<
- self.country_select(method, priority_countries, strip_formtastic_options(options), html_options)
- end
-
- # Outputs a label containing a checkbox and the label text. The label defaults
- # to the column name (method name) and can be altered with the :label option.
- # :checked_value and :unchecked_value options are also available.
- #
- # You can pre-select/check the boolean checkbox by passing in the :selected option (alias :checked works as well).
- #
- # Examples:
- #
- # f.input :allow_comments, :as => :boolean, :selected => true # override any default value: selected/checked
- #
- def boolean_input(method, options)
- html_options = options.delete(:input_html) || {}
- checked = options.key?(:checked) ? options[:checked] : options[:selected]
- html_options[:checked] = checked == true if [:selected, :checked].any? { |k| options.key?(k) }
-
- input = self.check_box(method, strip_formtastic_options(options).merge(html_options),
- options.delete(:checked_value) || '1', options.delete(:unchecked_value) || '0')
- options = options_for_label(options)
-
- # the label() method will insert this nested input into the label at the last minute
- options[:label_prefix_for_nested_input] = input
-
- self.label(method, options)
- end
-
- # Generates an input for the given method using the type supplied with :as.
- def inline_input_for(method, options)
- send(:"#{options.delete(:as)}_input", method, options)
- end
-
- # Generates hints for the given method using the text supplied in :hint.
- #
- def inline_hints_for(method, options) #:nodoc:
- options[:hint] = localized_string(method, options[:hint], :hint)
- return if options[:hint].blank?
- template.content_tag(:p, options[:hint], :class => 'inline-hints')
- end
-
- # Creates an error sentence by calling to_sentence on the errors array.
- #
- def error_sentence(errors) #:nodoc:
- template.content_tag(:p, errors.to_sentence.untaint, :class => 'inline-errors')
- end
-
- # Creates an error li list.
- #
- def error_list(errors) #:nodoc:
- list_elements = []
- errors.each do |error|
- list_elements << template.content_tag(:li, error.untaint)
- end
- template.content_tag(:ul, list_elements.join("\n"), :class => 'errors')
- end
-
- # Creates an error sentence containing only the first error
- #
- def error_first(errors) #:nodoc:
- template.content_tag(:p, errors.first.untaint, :class => 'inline-errors')
- end
-
- # Generates the required or optional string. If the value set is a proc,
- # it evaluates the proc first.
- #
- def required_or_optional_string(required) #:nodoc:
- string_or_proc = case required
- when true
- @@required_string
- when false
- @@optional_string
- else
- required
- end
-
- if string_or_proc.is_a?(Proc)
- string_or_proc.call
- else
- string_or_proc.to_s
- end
- end
-
- # Generates a fieldset and wraps the content in an ordered list. When working
- # with nested attributes (in Rails 2.3), it allows %i as interpolation option
- # in :name. So you can do:
- #
- # f.inputs :name => 'Task #%i', :for => :tasks
- #
- # or the shorter equivalent:
- #
- # f.inputs 'Task #%i', :for => :tasks
- #
- # And it will generate a fieldset for each task with legend 'Task #1', 'Task #2',
- # 'Task #3' and so on.
- #
- # Note: Special case for the inline inputs (non-block):
- # f.inputs "My little legend", :title, :body, :author # Explicit legend string => "My little legend"
- # f.inputs :my_little_legend, :title, :body, :author # Localized (118n) legend with I18n key => I18n.t(:my_little_legend, ...)
- # f.inputs :title, :body, :author # First argument is a column => (no legend)
- #
- def field_set_and_list_wrapping(*args, &block) #:nodoc:
- contents = args.last.is_a?(::Hash) ? '' : args.pop.flatten
- html_options = args.extract_options!
-
- legend = html_options.delete(:name).to_s
- legend %= parent_child_index(html_options[:parent]) if html_options[:parent]
- legend = template.content_tag(:legend, template.content_tag(:span, legend)) unless legend.blank?
-
- if block_given?
- contents = if template.respond_to?(:is_haml?) && template.is_haml?
- template.capture_haml(&block)
- else
- template.capture(&block)
- end
- end
-
- # Ruby 1.9: String#to_s behavior changed, need to make an explicit join.
- contents = contents.join if contents.respond_to?(:join)
- fieldset = template.content_tag(:fieldset,
- legend << template.content_tag(:ol, contents),
- html_options.except(:builder, :parent)
- )
-
- template.concat(fieldset) if block_given?
- fieldset
- end
-
- def field_set_title_from_args(*args) #:nodoc:
- options = args.extract_options!
- options[:name] ||= options.delete(:title)
- title = options[:name]
-
- if title.blank?
- valid_name_classes = [::String, ::Symbol]
- valid_name_classes.delete(::Symbol) if !block_given? && (args.first.is_a?(::Symbol) && self.content_columns.include?(args.first))
- title = args.shift if valid_name_classes.any? { |valid_name_class| args.first.is_a?(valid_name_class) }
- end
- title = localized_string(title, title, :title) if title.is_a?(::Symbol)
- title
- end
-
- # Also generates a fieldset and an ordered list but with label based in
- # method. This methods is currently used by radio and datetime inputs.
- #
- def field_set_and_list_wrapping_for_method(method, options, contents) #:nodoc:
- contents = contents.join if contents.respond_to?(:join)
-
- template.content_tag(:fieldset,
- template.content_tag(:legend,
- self.label(method, options_for_label(options).merge(:for => options.delete(:label_for))), :class => 'label'
- ) <<
- template.content_tag(:ol, contents)
- )
- end
-
- # For methods that have a database column, take a best guess as to what the input method
- # should be. In most cases, it will just return the column type (eg :string), but for special
- # cases it will simplify (like the case of :integer, :float & :decimal to :numeric), or do
- # something different (like :password and :select).
- #
- # If there is no column for the method (eg "virtual columns" with an attr_accessor), the
- # default is a :string, a similar behaviour to Rails' scaffolding.
- #
- def default_input_type(method, options = {}) #:nodoc:
- if column = self.column_for(method)
- # Special cases where the column type doesn't map to an input method.
- case column.type
- when :string
- return :password if method.to_s =~ /password/
- return :country if method.to_s =~ /country/
- return :time_zone if method.to_s =~ /time_zone/
- when :integer
- return :select if method.to_s =~ /_id$/
- return :numeric
- when :float, :decimal
- return :numeric
- when :timestamp
- return :datetime
- end
-
- # Try look for hints in options hash. Quite common senario: Enum keys stored as string in the database.
- return :select if column.type == :string && options.key?(:collection)
- # Try 3: Assume the input name will be the same as the column type (e.g. string_input).
- return column.type
- else
- if @object
- return :select if self.reflection_for(method)
-
- file = @object.send(method) if @object.respond_to?(method)
- return :file if file && @@file_methods.any? { |m| file.respond_to?(m) }
- end
-
- return :select if options.key?(:collection)
- return :password if method.to_s =~ /password/
- return :string
- end
- end
-
- # Used by select and radio inputs. The collection can be retrieved by
- # three ways:
- #
- # * Explicitly provided through :collection
- # * Retrivied through an association
- # * Or a boolean column, which will generate a localized { "Yes" => true, "No" => false } hash.
- #
- # If the collection is not a hash or an array of strings, fixnums or arrays,
- # we use label_method and value_method to retreive an array with the
- # appropriate label and value.
- #
- def find_collection_for_column(column, options) #:nodoc:
- collection = find_raw_collection_for_column(column, options)
-
- # Return if we have an Array of strings, fixnums or arrays
- return collection if (collection.instance_of?(Array) || collection.instance_of?(Range)) &&
- [Array, Fixnum, String, Symbol].include?(collection.first.class)
-
- label, value = detect_label_and_value_method!(collection, options)
- collection.map { |o| [send_or_call(label, o), send_or_call(value, o)] }
- end
-
- # As #find_collection_for_column but returns the collection without mapping the label and value
- #
- def find_raw_collection_for_column(column, options) #:nodoc:
- collection = if options[:collection]
- options.delete(:collection)
- elsif reflection = self.reflection_for(column)
- reflection.klass.find(:all, options[:find_options] || {})
- else
- create_boolean_collection(options)
- end
-
- collection = collection.to_a if collection.is_a?(Hash)
- collection
- end
-
- # Detects the label and value methods from a collection values set in
- # @@collection_label_methods. It will use and delete
- # the options :label_method and :value_methods when present
- #
- def detect_label_and_value_method!(collection_or_instance, options = {}) #:nodoc
- label = options.delete(:label_method) || detect_label_method(collection_or_instance)
- value = options.delete(:value_method) || :id
- [label, value]
- end
-
- # Detected the label collection method when none is supplied using the
- # values set in @@collection_label_methods.
- #
- def detect_label_method(collection) #:nodoc:
- @@collection_label_methods.detect { |m| collection.first.respond_to?(m) }
- end
-
- # Detects the method to call for fetching group members from the groups when grouping select options
- #
- def detect_group_association(method, group_by)
- object_to_method_reflection = self.reflection_for(method)
- method_class = object_to_method_reflection.klass
-
- method_to_group_association = method_class.reflect_on_association(group_by)
- group_class = method_to_group_association.klass
-
- # This will return in the normal case
- return method.to_s.pluralize.to_sym if group_class.reflect_on_association(method.to_s.pluralize)
-
- # This is for belongs_to associations named differently than their class
- # form.input :parent, :group_by => :customer
- # eg.
- # class Project
- # belongs_to :parent, :class_name => 'Project', :foreign_key => 'parent_id'
- # belongs_to :customer
- # end
- # class Customer
- # has_many :projects
- # end
- group_method = method_class.to_s.underscore.pluralize.to_sym
- return group_method if group_class.reflect_on_association(group_method) # :projects
-
- # This is for has_many associations named differently than their class
- # eg.
- # class Project
- # belongs_to :parent, :class_name => 'Project', :foreign_key => 'parent_id'
- # belongs_to :customer
- # end
- # class Customer
- # has_many :tasks, :class_name => 'Project', :foreign_key => 'customer_id'
- # end
- possible_associations = group_class.reflect_on_all_associations(:has_many).find_all{|assoc| assoc.klass == object_class}
- return possible_associations.first.name.to_sym if possible_associations.count == 1
-
- raise "Cannot infer group association for #{method} grouped by #{group_by}, there were #{possible_associations.empty? ? 'no' : possible_associations.size} possible associations. Please specify using :group_association"
-
- end
-
- # Returns a hash to be used by radio and select inputs when a boolean field
- # is provided.
- #
- def create_boolean_collection(options) #:nodoc:
- options[:true] ||= ::Formtastic::I18n.t(:yes)
- options[:false] ||= ::Formtastic::I18n.t(:no)
- options[:value_as_class] = true unless options.key?(:value_as_class)
-
- [ [ options.delete(:true), true], [ options.delete(:false), false ] ]
- end
-
- # Used by association inputs (select, radio) to generate the name that should
- # be used for the input
- #
- # belongs_to :author; f.input :author; will generate 'author_id'
- # belongs_to :entity, :foreign_key = :owner_id; f.input :author; will generate 'owner_id'
- # has_many :authors; f.input :authors; will generate 'author_ids'
- # has_and_belongs_to_many will act like has_many
- #
- def generate_association_input_name(method) #:nodoc:
- if reflection = self.reflection_for(method)
- if [:has_and_belongs_to_many, :has_many].include?(reflection.macro)
- "#{method.to_s.singularize}_ids"
- else
- reflection.options[:foreign_key] || reflection.options[:class_name].try(:foreign_key) || "#{method}_id"
- end
- else
- method
- end.to_sym
- end
-
- # If an association method is passed in (f.input :author) try to find the
- # reflection object.
- #
- def reflection_for(method) #:nodoc:
- @object.class.reflect_on_association(method) if @object.class.respond_to?(:reflect_on_association)
- end
-
- # Get a column object for a specified attribute method - if possible.
- #
- def column_for(method) #:nodoc:
- @object.column_for_attribute(method) if @object.respond_to?(:column_for_attribute)
- end
-
- # Generates default_string_options by retrieving column information from
- # the database.
- #
- def default_string_options(method, type) #:nodoc:
- column = self.column_for(method)
-
- if type == :text
- { :cols => @@default_text_field_size, :rows => @@default_text_area_height }
- elsif type == :numeric || column.nil? || column.limit.nil?
- { :size => @@default_text_field_size }
- else
- { :maxlength => column.limit, :size => [column.limit, @@default_text_field_size].min }
- end
- end
-
- # Generate the html id for the li tag.
- # It takes into account options[:index] and @auto_index to generate li
- # elements with appropriate index scope. It also sanitizes the object
- # and method names.
- #
- def generate_html_id(method_name, value='input') #:nodoc:
- if options.has_key?(:index)
- index = "_#{options[:index]}"
- elsif defined?(@auto_index)
- index = "_#{@auto_index}"
- else
- index = ""
- end
- sanitized_method_name = method_name.to_s.gsub(/[\?\/\-]$/, '')
-
- "#{sanitized_object_name}#{index}_#{sanitized_method_name}_#{value}"
- end
-
- # Gets the nested_child_index value from the parent builder. In Rails 2.3
- # it always returns a fixnum. In next versions it returns a hash with each
- # association that the parent builds.
- #
- def parent_child_index(parent) #:nodoc:
- duck = parent[:builder].instance_variable_get('@nested_child_index')
-
- if duck.is_a?(Hash)
- child = parent[:for]
- child = child.first if child.respond_to?(:first)
- duck[child].to_i + 1
- else
- duck.to_i + 1
- end
- end
-
- def sanitized_object_name #:nodoc:
- @sanitized_object_name ||= @object_name.to_s.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
- end
-
- def humanized_attribute_name(method) #:nodoc:
- if @object && @object.class.respond_to?(:human_attribute_name)
- humanized_name = @object.class.human_attribute_name(method.to_s)
- if humanized_name == method.to_s.send(:humanize)
- method.to_s.send(@@label_str_method)
- else
- humanized_name
- end
- else
- method.to_s.send(@@label_str_method)
- end
- end
-
- # Internal generic method for looking up localized values within Formtastic
- # using I18n, if no explicit value is set and I18n-lookups are enabled.
- #
- # Enabled/Disable this by setting:
- #
- # Formtastic::SemanticFormBuilder.i18n_lookups_by_default = true/false
- #
- # Lookup priority:
- #
- # 'formtastic.{{type}}.{{model}}.{{action}}.{{attribute}}'
- # 'formtastic.{{type}}.{{model}}.{{attribute}}'
- # 'formtastic.{{type}}.{{attribute}}'
- #
- # Example:
- #
- # 'formtastic.labels.post.edit.title'
- # 'formtastic.labels.post.title'
- # 'formtastic.labels.title'
- #
- # NOTE: Generic, but only used for form input titles/labels/hints/actions (titles = legends, actions = buttons).
- #
- def localized_string(key, value, type, options = {}) #:nodoc:
- key = value if value.is_a?(::Symbol)
-
- if value.is_a?(::String)
- value
- else
- use_i18n = value.nil? ? @@i18n_lookups_by_default : (value != false)
-
- if use_i18n
- model_name = self.model_name.underscore
- action_name = template.params[:action].to_s rescue ''
- attribute_name = key.to_s
-
- defaults = ::Formtastic::I18n::SCOPES.collect do |i18n_scope|
- i18n_path = i18n_scope.dup
- i18n_path.gsub!('{{action}}', action_name)
- i18n_path.gsub!('{{model}}', model_name)
- i18n_path.gsub!('{{attribute}}', attribute_name)
- i18n_path.gsub!('..', '.')
- i18n_path.to_sym
- end
- defaults << ''
-
- i18n_value = ::Formtastic::I18n.t(defaults.shift,
- options.merge(:default => defaults, :scope => type.to_s.pluralize.to_sym))
- i18n_value.blank? ? nil : i18n_value
- end
- end
- end
-
- def model_name
- @object.present? ? @object.class.name : @object_name.to_s.classify
- end
-
- def send_or_call(duck, object)
- if duck.is_a?(Proc)
- duck.call(object)
- else
- object.send(duck)
- end
- end
-
- def set_include_blank(options)
- unless options.key?(:include_blank) || options.key?(:prompt)
- options[:include_blank] = @@include_blank_for_select_by_default
- end
- options
- end
-
+# encoding: utf-8
+require 'formtastic/railtie' if defined?(::Rails)
+require 'formtastic/engine' if defined?(::Rails)
+
+module Formtastic
+ extend ActiveSupport::Autoload
+
+ autoload :FormBuilder
+ autoload :Helpers
+ autoload :HtmlAttributes
+ autoload :I18n
+ autoload :Inputs
+ autoload :LocalizedString
+ autoload :Localizer
+ autoload :Util
+
+ # @private
+ class UnknownInputError < NameError
end
-
- # Wrappers around form_for (etc) with :builder => SemanticFormBuilder.
- #
- # * semantic_form_for(@post)
- # * semantic_fields_for(@post)
- # * semantic_form_remote_for(@post)
- # * semantic_remote_form_for(@post)
- #
- # Each of which are the equivalent of:
- #
- # * form_for(@post, :builder => Formtastic::SemanticFormBuilder))
- # * fields_for(@post, :builder => Formtastic::SemanticFormBuilder))
- # * form_remote_for(@post, :builder => Formtastic::SemanticFormBuilder))
- # * remote_form_for(@post, :builder => Formtastic::SemanticFormBuilder))
- #
- # Example Usage:
- #
- # <% semantic_form_for @post do |f| %>
- # <%= f.input :title %>
- # <%= f.input :body %>
- # <% end %>
- #
- # The above examples use a resource-oriented style of form_for() helper where only the @post
- # object is given as an argument, but the generic style is also supported, as are forms with
- # inline objects (Post.new) rather than objects with instance variables (@post):
- #
- # <% semantic_form_for :post, @post, :url => posts_path do |f| %>
- # ...
- # <% end %>
- #
- # <% semantic_form_for :post, Post.new, :url => posts_path do |f| %>
- # ...
- # <% end %>
- module SemanticFormHelper
- @@builder = ::Formtastic::SemanticFormBuilder
- mattr_accessor :builder
-
- @@default_field_error_proc = nil
-
- # Override the default ActiveRecordHelper behaviour of wrapping the input.
- # This gets taken care of semantically by adding an error class to the LI tag
- # containing the input.
- #
- FIELD_ERROR_PROC = proc do |html_tag, instance_tag|
- html_tag
- end
-
- def with_custom_field_error_proc(&block)
- @@default_field_error_proc = ::ActionView::Base.field_error_proc
- ::ActionView::Base.field_error_proc = FIELD_ERROR_PROC
- result = yield
- ::ActionView::Base.field_error_proc = @@default_field_error_proc
- result
- end
-
- [:form_for, :fields_for, :remote_form_for].each do |meth|
- module_eval <<-END_SRC, __FILE__, __LINE__ + 1
- def semantic_#{meth}(record_or_name_or_array, *args, &proc)
- options = args.extract_options!
- options[:builder] ||= @@builder
- options[:html] ||= {}
-
- class_names = options[:html][:class] ? options[:html][:class].split(" ") : []
- class_names << "formtastic"
- class_names << case record_or_name_or_array
- when String, Symbol then record_or_name_or_array.to_s # :post => "post"
- when Array then ActionController::RecordIdentifier.singular_class_name(record_or_name_or_array.last.class) # [@post, @comment] # => "comment"
- else ActionController::RecordIdentifier.singular_class_name(record_or_name_or_array.class) # @post => "post"
- end
- options[:html][:class] = class_names.join(" ")
-
- with_custom_field_error_proc do
- #{meth}(record_or_name_or_array, *(args << options), &proc)
- end
- end
- END_SRC
- end
- alias :semantic_form_remote_for :semantic_remote_form_for
-
+
+ # @private
+ class PolymorphicInputWithoutCollectionError < ArgumentError
end
+
end
diff --git a/lib/formtastic/engine.rb b/lib/formtastic/engine.rb
new file mode 100644
index 000000000..05dae9a0a
--- /dev/null
+++ b/lib/formtastic/engine.rb
@@ -0,0 +1,7 @@
+# Configure Rails 3.1 to have assert_select_jquery() in tests
+module Formtastic
+ # Required for formtastic.css to be discoverable in the asset pipeline
+ # @private
+ class Engine < ::Rails::Engine
+ end
+end
\ No newline at end of file
diff --git a/lib/formtastic/form_builder.rb b/lib/formtastic/form_builder.rb
new file mode 100644
index 000000000..58fad6b7e
--- /dev/null
+++ b/lib/formtastic/form_builder.rb
@@ -0,0 +1,86 @@
+module Formtastic
+ class FormBuilder < ActionView::Helpers::FormBuilder
+
+ def self.configure(name, value = nil)
+ class_attribute(name)
+ self.send(:"#{name}=", value)
+ end
+
+ configure :custom_namespace
+ configure :default_text_field_size
+ configure :default_text_area_height, 20
+ configure :default_text_area_width
+ configure :all_fields_required_by_default, true
+ configure :include_blank_for_select_by_default, true
+ configure :required_string, proc { Formtastic::Util.html_safe(%{* }) }
+ configure :optional_string, ''
+ configure :inline_errors, :sentence
+ configure :label_str_method, :humanize
+ configure :collection_label_methods, %w[to_label display_name full_name name title username login value to_s]
+ configure :collection_value_methods, %w[id to_s]
+ configure :custom_inline_order, {}
+ configure :file_methods, [ :file?, :public_filename, :filename ]
+ configure :file_metadata_suffixes, ['content_type', 'file_name', 'file_size']
+ configure :priority_countries, ["Australia", "Canada", "United Kingdom", "United States"]
+ configure :i18n_lookups_by_default, true
+ configure :i18n_localizer, Formtastic::Localizer
+ configure :escape_html_entities_in_hints_and_labels, true
+ configure :default_commit_button_accesskey
+ configure :default_inline_error_class, 'inline-errors'
+ configure :default_error_list_class, 'errors'
+ configure :default_hint_class, 'inline-hints'
+ configure :use_required_attribute, false
+ configure :perform_browser_validations, false
+
+ attr_reader :template
+
+ attr_reader :auto_index
+
+ include Formtastic::HtmlAttributes
+
+ include Formtastic::Helpers::InputHelper
+ include Formtastic::Helpers::InputsHelper
+ include Formtastic::Helpers::ButtonsHelper
+ include Formtastic::Helpers::ErrorsHelper
+
+ # This is a wrapper around Rails' `ActionView::Helpers::FormBuilder#fields_for`, originally
+ # provided to ensure that the `:builder` from `semantic_form_for` was passed down into
+ # the nested `fields_for`. Rails 3 no longer requires us to do this, so this method is
+ # provided purely for backwards compatibility and DSL consistency.
+ #
+ # When constructing a `fields_for` form fragment *outside* of `semantic_form_for`, please use
+ # `Formtastic::Helpers::FormHelper#semantic_fields_for`.
+ #
+ # @see http://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html#method-i-fields_for ActionView::Helpers::FormBuilder#fields_for
+ # @see http://api.rubyonrails.org/classes/ActionView/Helpers/FormHelper.html#method-i-fields_for ActionView::Helpers::FormHelper#fields_for
+ # @see Formtastic::Helpers::FormHelper#semantic_fields_for
+ #
+ # @example
+ # <% semantic_form_for @post do |post| %>
+ # <% post.semantic_fields_for :author do |author| %>
+ # <% author.inputs :name %>
+ # <% end %>
+ # <% end %>
+ #
+ #
+ #
+ # @todo is there a way to test the params structure of the Rails helper we wrap to ensure forward compatibility?
+ def semantic_fields_for(record_or_name_or_array, *args, &block)
+ # Add a :parent_builder to the args so that nested translations can be possible in Rails 3
+ options = args.extract_options!
+ options[:parent_builder] ||= self
+
+ # Wrap the Rails helper
+ fields_for(record_or_name_or_array, *(args << options), &block)
+ end
+
+ end
+
+end
+
diff --git a/lib/formtastic/helpers.rb b/lib/formtastic/helpers.rb
new file mode 100644
index 000000000..71eca2b1a
--- /dev/null
+++ b/lib/formtastic/helpers.rb
@@ -0,0 +1,15 @@
+module Formtastic
+ # @private
+ module Helpers
+ autoload :ButtonsHelper, 'formtastic/helpers/buttons_helper'
+ autoload :ErrorsHelper, 'formtastic/helpers/errors_helper'
+ autoload :FieldsetWrapper, 'formtastic/helpers/fieldset_wrapper'
+ autoload :FileColumnDetection, 'formtastic/helpers/file_column_detection'
+ autoload :FormHelper, 'formtastic/helpers/form_helper'
+ autoload :InputHelper, 'formtastic/helpers/input_helper'
+ autoload :InputsHelper, 'formtastic/helpers/inputs_helper'
+ autoload :LabelHelper, 'formtastic/helpers/label_helper'
+ autoload :Reflection, 'formtastic/helpers/reflection'
+ end
+end
+
diff --git a/lib/formtastic/helpers/buttons_helper.rb b/lib/formtastic/helpers/buttons_helper.rb
new file mode 100644
index 000000000..89d017bc1
--- /dev/null
+++ b/lib/formtastic/helpers/buttons_helper.rb
@@ -0,0 +1,301 @@
+module Formtastic
+ module Helpers
+
+ # ButtonsHelper encapsulates the responsibilties of the {#buttons} and {#commit_button} helpers
+ # for submitting forms.
+ #
+ # {#buttons} is used to wrap the form's button(s) and actions in a `` and ``,
+ # with each item in the list containing the markup representing a single button.
+ #
+ # {#buttons} is usually called with a block containing a single {#commit_button} call:
+ #
+ # <%= semantic_form_for @post do |f| %>
+ # ...
+ # <%= f.buttons do %>
+ # <%= f.commit_button
+ # <% end %>
+ # <% end %>
+ #
+ # The HTML output will be something like:
+ #
+ #
+ #
+ # While this may seem slightly over-engineered, it is consistent with the way form inputs are
+ # handled, and makes room for other types of buttons and actions in future versions (such as
+ # cancel buttons or links, reset buttons and even alternate actions like 'save and continue
+ # editing').
+ #
+ # It's important to note that the `semantic_form_for` and {#buttons} blocks wrap the
+ # standard Rails `form_for` helper and form builder, so you have full access to every standard
+ # Rails form helper, with any HTML markup and ERB syntax, allowing you to "break free" from
+ # Formtastic when it doesn't suit to create your own buttons, links and actions:
+ #
+ # <%= semantic_form_for @post do |f| %>
+ # ...
+ # <%= f.buttons do %>
+ #
+ # <%= f.submit "Save" %>
+ #
+ #
+ # Or <%= link_to "Cancel", posts_url %>
+ #
+ # <% end %>
+ # <% end %>
+ #
+ # There are many other syntax variations and arguments to customize your form. See the
+ # full documentation of {#buttons} and {#commit_button} for details.
+ module ButtonsHelper
+ include Formtastic::Helpers::FieldsetWrapper
+ include Formtastic::LocalizedString
+
+ # Creates a fieldset and ol tag wrapping for use around a set of buttons. It can be
+ # called either with a block (in which you can do the usual Rails form stuff, HTML, ERB, etc),
+ # or with a list of named buttons. These two examples are functionally equivalent:
+ #
+ # # With a block:
+ # <% semantic_form_for @post do |f| %>
+ # ...
+ # <% f.buttons do %>
+ # <%= f.commit_button %>
+ # <% end %>
+ # <% end %>
+ #
+ # # With a list of fields:
+ # <% semantic_form_for @post do |f| %>
+ # <%= f.buttons :commit %>
+ # <% end %>
+ #
+ # # Output:
+ #
+ #
+ # Only one type of named button is supported at this time (:commit), and it's assumed to be
+ # the default choice, so this is also functionally equivalent, but may change in the future:
+ #
+ # # With no args:
+ # <% semantic_form_for @post do |f| %>
+ # ...
+ # <%= f.buttons %>
+ # <% end %>
+ #
+ # While this may seem slightly over-engineered, it is consistent with the way form inputs are
+ # handled, and makes room for other types of buttons and actions in future versions (such as
+ # cancel buttons or links, reset buttons and even alternate actions like 'save and continue
+ # editing').
+ #
+ # All options except `:name` and `:title` are passed down to the fieldset as HTML
+ # attributes (`id`, `class`, `style`...). If provided, the `:name` or `:title` option is
+ # passed into a `` inside the ` ` to name the set of buttons.
+ #
+ # @example Quickly add button(s) to the form, accepting all default values, options and behaviors
+ # <% semantic_form_for @post do |f| %>
+ # ...
+ # <%= f.buttons %>
+ # <% end %>
+ #
+ # @example Specify which named buttons you want, accepting all default values, options and behaviors
+ # <% semantic_form_for @post do |f| %>
+ # ...
+ # <%= f.buttons :commit %>
+ # <% end %>
+ #
+ # @example Specify which named buttons you want, and name the fieldset
+ # <% semantic_form_for @post do |f| %>
+ # ...
+ # <%= f.buttons :commit, :name => "Actions" %>
+ # or
+ # <%= f.buttons :commit, :label => "Actions" %>
+ # <% end %>
+ #
+ # @example Get full control over the commit_button options
+ # <% semantic_form_for @post do |f| %>
+ # ...
+ # <%= f.buttons do %>
+ # <%= f.commit_button :label => "Go", :button_html => { :class => "pretty" :disable_with => "Wait..." }, :wrapper_html => { ... }
+ # <% end %>
+ # <% end %>
+ #
+ # @example Make your own custom buttons, links or actions with standard Rails helpers or HTML
+ # <% semantic_form_for @post do |f| %>
+ # ...
+ # <%= f.buttons do %>
+ #
+ # <%= f.submit "Submit" %>
+ #
+ #
+ #
+ #
+ #
+ # <%= link_to "Cancel", posts_url %>
+ #
+ # <% end %>
+ # <% end %>
+ #
+ # @example Add HTML attributes to the fieldset
+ # <% semantic_form_for @post do |f| %>
+ # ...
+ # <%= f.buttons :commit, :style => "border:1px;" %>
+ # or
+ # <%= f.buttons :style => "border:1px;" do %>
+ # ...
+ # <% end %>
+ # <% end %>
+ #
+ # @option *args :label [String, Symbol]
+ # Optionally specify text for the legend of the fieldset
+ #
+ # @option *args :name [String, Symbol]
+ # Optionally specify text for the legend of the fieldset (alias for `:label`)
+ #
+ # @todo document i18n keys
+ def buttons(*args, &block)
+ html_options = args.extract_options!
+ html_options[:class] ||= "buttons"
+
+ if block_given?
+ field_set_and_list_wrapping(html_options, &block)
+ else
+ args = [:commit] if args.empty?
+ contents = args.map { |button_name| send(:"#{button_name}_button") }
+ field_set_and_list_wrapping(html_options, contents)
+ end
+ end
+
+ # Creates a submit input tag with the value "Save [model name]" (for existing records) or
+ # "Create [model name]" (for new records) by default. The output is an ` ` tag with the
+ # `type` of `submit` and a class of either `create` or `update` (if Formtastic can determin if)
+ # the record is new or not) with `submit` as a fallback class. The submit button is wrapped in
+ # an `` tag with a class of `commit`, and is intended to be rendered inside a {#buttons}
+ # block which wraps the button in a `fieldset` and `ol`.
+ #
+ # The textual value of the label can be changed from this default through the `:label`
+ # argument or through i18n.
+ #
+ # You can pass HTML attributes down to the ` ` tag with the `:button_html` option, and
+ # pass HTML attributes to the wrapping ` ` tag with the `:wrapper_html` option.
+ #
+ # @example Basic usage
+ # # form
+ # <%= semantic_form_for @post do |f| %>
+ # ...
+ # <%= f.buttons do %>
+ # <%= f.commit_button %>
+ # <% end %>
+ # <% end %>
+ #
+ # # output
+ #
+ #
+ # @example Set the value through the `:label` option
+ # <%= f.commit_button :label => "Go" %>
+ #
+ # @example Set the value through the optional first argument (like Rails' `f.submit`)
+ # <%= f.commit_button "Go" %>
+ #
+ # @example Pass HTML attributes down to the ` `
+ # <%= f.commit_button :button_html => { :class => 'pretty', :accesskey => 'g', :disable_with => "Wait..." } %>
+ # <%= f.commit_button :label => "Go", :button_html => { :class => 'pretty', :accesskey => 'g', :disable_with => "Wait..." } %>
+ # <%= f.commit_button "Go", :button_html => { :class => 'pretty', :accesskey => 'g', :disable_with => "Wait..." } %>
+ #
+ # @example Pass HTML attributes down to the ` ` wrapper
+ # <%= f.commit_button :wrapper_html => { :class => 'special', :id => 'whatever' } %>
+ # <%= f.commit_button :label => "Go", :wrapper_html => { :class => 'special', :id => 'whatever' } %>
+ # <%= f.commit_button "Go", :wrapper_html => { :class => 'special', :id => 'whatever' } %>
+ #
+ # @option *args :label [String, Symbol]
+ # Override the label text with a String or a symbold for an i18n translation key
+ #
+ # @option *args :button_html [Hash]
+ # Override or add to the HTML attributes to be passed down to the ` ` tag
+ #
+ # @option *args :wrapper_html [Hash]
+ # Override or add to the HTML attributes to be passed down to the wrapping ` ` tag
+ #
+ # @todo document i18n keys
+ # @todo strange that `:accesskey` seems to be supported in the top level args as well as `:button_html`
+ def commit_button(*args)
+ options = args.extract_options!
+ text = options.delete(:label) || args.shift
+
+ text = (localized_string(commit_button_i18n_key, text, :action, :model => commit_button_object_name) ||
+ Formtastic::I18n.t(commit_button_i18n_key, :model => commit_button_object_name)) unless text.is_a?(::String)
+
+ button_html = options.delete(:button_html) || {}
+ button_html.merge!(:class => [button_html[:class], commit_button_i18n_key].compact.join(' '))
+
+ wrapper_html = options.delete(:wrapper_html) || {}
+ wrapper_html[:class] = (commit_button_wrapper_html_class << wrapper_html[:class]).flatten.compact.join(' ')
+
+ accesskey = (options.delete(:accesskey) || default_commit_button_accesskey) unless button_html.has_key?(:accesskey)
+ button_html = button_html.merge(:accesskey => accesskey) if accesskey
+
+ template.content_tag(:li, Formtastic::Util.html_safe(submit(text, button_html)), wrapper_html)
+ end
+
+ def commit_button_object_name
+ if new_or_persisted_object?
+ # Deal with some complications with ActiveRecord::Base.human_name and two name models (eg UserPost)
+ # ActiveRecord::Base.human_name falls back to ActiveRecord::Base.name.humanize ("Userpost")
+ # if there's no i18n, which is pretty crappy. In this circumstance we want to detect this
+ # fall back (human_name == name.humanize) and do our own thing name.underscore.humanize ("User Post")
+ if @object.class.model_name.respond_to?(:human)
+ object_name = @object.class.model_name.human
+ else
+ object_human_name = @object.class.human_name # default is UserPost => "Userpost", but i18n may do better ("User post")
+ crappy_human_name = @object.class.name.humanize # UserPost => "Userpost"
+ decent_human_name = @object.class.name.underscore.humanize # UserPost => "User post"
+ object_name = (object_human_name == crappy_human_name) ? decent_human_name : object_human_name
+ end
+ else
+ object_name = @object_name.to_s.send(label_str_method)
+ end
+
+ object_name
+ end
+
+ def commit_button_i18n_key
+ if new_or_persisted_object?
+ key = @object.persisted? ? :update : :create
+ else
+ key = :submit
+ end
+
+ key
+ end
+
+ def commit_button_wrapper_html_class
+ ['commit', 'button'] # TODO: Add class reflecting on form action.
+ end
+
+ def new_or_persisted_object?
+ @object && (@object.respond_to?(:persisted?) || @object.respond_to?(:new_record?))
+ end
+
+ end
+ end
+end
\ No newline at end of file
diff --git a/lib/formtastic/helpers/errors_helper.rb b/lib/formtastic/helpers/errors_helper.rb
new file mode 100644
index 000000000..c08f06012
--- /dev/null
+++ b/lib/formtastic/helpers/errors_helper.rb
@@ -0,0 +1,81 @@
+module Formtastic
+ module Helpers
+ module ErrorsHelper
+ include Formtastic::Helpers::FileColumnDetection
+ include Formtastic::Helpers::Reflection
+ include Formtastic::LocalizedString
+
+ INLINE_ERROR_TYPES = [:sentence, :list, :first]
+
+ # Generates an unordered list of error messages on the base object and optionally for a given
+ # set of named attribute. This is idea for rendering a block of error messages at the top of
+ # the form for hidden/special/virtual attributes (the Paperclip Rails plugin does this), or
+ # errors on the base model.
+ #
+ # A hash can be used as the last set of arguments to pass HTML attributes to the ``
+ # wrapper.
+ #
+ # @example A list of errors on the base model
+ # <%= semantic_form_for ... %>
+ # <%= f.semantic_errors %>
+ # ...
+ # <% end %>
+ #
+ # @example A list of errors on the base and named attributes
+ # <%= semantic_form_for ... %>
+ # <%= f.semantic_errors :something_special %>
+ # ...
+ # <% end %>
+ #
+ # @example A list of errors on the base model, with custom HTML attributes
+ # <%= semantic_form_for ... %>
+ # <%= f.semantic_errors :class => "awesome" %>
+ # ...
+ # <% end %>
+ #
+ # @example A list of errors on the base model and named attributes, with custom HTML attributes
+ # <%= semantic_form_for ... %>
+ # <%= f.semantic_errors :something_special, :something_else, :class => "awesome", :onclick => "Awesome();" %>
+ # ...
+ # <% end %>
+ def semantic_errors(*args)
+ html_options = args.extract_options!
+ args = args - [:base]
+ full_errors = args.inject([]) do |array, method|
+ attribute = localized_string(method, method.to_sym, :label) || humanized_attribute_name(method)
+ errors = Array(@object.errors[method.to_sym]).to_sentence
+ errors.present? ? array << [attribute, errors].join(" ") : array ||= []
+ end
+ full_errors << @object.errors[:base]
+ full_errors.flatten!
+ full_errors.compact!
+ return nil if full_errors.blank?
+ html_options[:class] ||= "errors"
+ template.content_tag(:ul, html_options) do
+ Formtastic::Util.html_safe(full_errors.map { |error| template.content_tag(:li, Formtastic::Util.html_safe(error)) }.join)
+ end
+ end
+
+ protected
+
+ def error_keys(method, options)
+ @methods_for_error ||= {}
+ @methods_for_error[method] ||= begin
+ methods_for_error = [method.to_sym]
+ methods_for_error << file_metadata_suffixes.map{|suffix| "#{method}_#{suffix}".to_sym} if is_file?(method, options)
+ methods_for_error << [association_primary_key_for_method(method)] if [:belongs_to, :has_many].include? association_macro_for_method(method)
+ methods_for_error.flatten.compact.uniq
+ end
+ end
+
+ def has_errors?(method, options)
+ methods_for_error = error_keys(method,options)
+ @object && @object.respond_to?(:errors) && methods_for_error.any?{|error| !@object.errors[error].blank?}
+ end
+
+ def render_inline_errors?
+ @object && @object.respond_to?(:errors) && Formtastic::FormBuilder::INLINE_ERROR_TYPES.include?(inline_errors)
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/lib/formtastic/helpers/fieldset_wrapper.rb b/lib/formtastic/helpers/fieldset_wrapper.rb
new file mode 100644
index 000000000..359ecf6c1
--- /dev/null
+++ b/lib/formtastic/helpers/fieldset_wrapper.rb
@@ -0,0 +1,80 @@
+module Formtastic
+ module Helpers
+ # @private
+ module FieldsetWrapper
+
+ protected
+
+ # Generates a fieldset and wraps the content in an ordered list. When working
+ # with nested attributes, it allows %i as interpolation option in :name. So you can do:
+ #
+ # f.inputs :name => 'Task #%i', :for => :tasks
+ #
+ # or the shorter equivalent:
+ #
+ # f.inputs 'Task #%i', :for => :tasks
+ #
+ # And it will generate a fieldset for each task with legend 'Task #1', 'Task #2',
+ # 'Task #3' and so on.
+ #
+ # Note: Special case for the inline inputs (non-block):
+ # f.inputs "My little legend", :title, :body, :author # Explicit legend string => "My little legend"
+ # f.inputs :my_little_legend, :title, :body, :author # Localized (118n) legend with I18n key => I18n.t(:my_little_legend, ...)
+ # f.inputs :title, :body, :author # First argument is a column => (no legend)
+ def field_set_and_list_wrapping(*args, &block) #:nodoc:
+ contents = args.last.is_a?(::Hash) ? '' : args.pop.flatten
+ html_options = args.extract_options!
+
+ if block_given?
+ contents = if template.respond_to?(:is_haml?) && template.is_haml?
+ template.capture_haml(&block)
+ else
+ template.capture(&block)
+ end
+ end
+
+ # Ruby 1.9: String#to_s behavior changed, need to make an explicit join.
+ contents = contents.join if contents.respond_to?(:join)
+
+ legend = field_set_legend(html_options)
+ fieldset = template.content_tag(:fieldset,
+ Formtastic::Util.html_safe(legend) << template.content_tag(:ol, Formtastic::Util.html_safe(contents)),
+ html_options.except(:builder, :parent, :name)
+ )
+
+ fieldset
+ end
+
+ def field_set_legend(html_options)
+ legend = (html_options[:name] || '').to_s
+ legend %= parent_child_index(html_options[:parent]) if html_options[:parent]
+ legend = template.content_tag(:legend, template.content_tag(:span, Formtastic::Util.html_safe(legend))) unless legend.blank?
+ legend
+ end
+
+ # Gets the nested_child_index value from the parent builder. It returns a hash with each
+ # association that the parent builds.
+ def parent_child_index(parent) #:nodoc:
+ # Could be {"post[authors_attributes]"=>0} or { :authors => 0 }
+ duck = parent[:builder].instance_variable_get('@nested_child_index')
+
+ # Could be symbol for the association, or a model (or an array of either, I think? TODO)
+ child = parent[:for]
+ # Pull a sybol or model out of Array (TODO: check if there's an Array)
+ child = child.first if child.respond_to?(:first)
+ # If it's an object, get a symbol from the class name
+ child = child.class.name.underscore.to_sym unless child.is_a?(Symbol)
+
+ key = "#{parent[:builder].object_name}[#{child}_attributes]"
+
+ # TODO: One of the tests produces a scenario where duck is "0" and the test looks for a "1"
+ # in the legend, so if we have a number, return it with a +1 until we can verify this scenario.
+ return duck + 1 if duck.is_a?(Fixnum)
+
+ # First try to extract key from duck Hash, then try child
+ i = (duck[key] || duck[child]).to_i + 1
+ end
+
+ end
+ end
+end
diff --git a/lib/formtastic/helpers/file_column_detection.rb b/lib/formtastic/helpers/file_column_detection.rb
new file mode 100644
index 000000000..48c39b12b
--- /dev/null
+++ b/lib/formtastic/helpers/file_column_detection.rb
@@ -0,0 +1,16 @@
+module Formtastic
+ module Helpers
+ # @private
+ module FileColumnDetection
+
+ def is_file?(method, options = {})
+ @files ||= {}
+ @files[method] ||= (options[:as].present? && options[:as] == :file) || begin
+ file = @object.send(method) if @object && @object.respond_to?(method)
+ file && file_methods.any?{|m| file.respond_to?(m)}
+ end
+ end
+
+ end
+ end
+end
\ No newline at end of file
diff --git a/lib/formtastic/helpers/form_helper.rb b/lib/formtastic/helpers/form_helper.rb
new file mode 100644
index 000000000..3d15390cf
--- /dev/null
+++ b/lib/formtastic/helpers/form_helper.rb
@@ -0,0 +1,200 @@
+module Formtastic
+ module Helpers
+
+ # FormHelper provides a handful of wrappers around Rails' built-in form helpers methods to set
+ # the `:builder` option to `Formtastic::FormBuilder` and apply some class names to the `
+ #
+ #
+ # @example `:time` input
+ # <%= f.input :publish_at, :as => :time %>
+ #
+ # @example `:datetime` input
+ # <%= f.input :publish_at, :as => :datetime %>
+ #
+ # @example Change the labels for each fragment
+ # <%= f.input :publish_at, :as => :date, :labels => { :year => "Y", :month => "M", :day => "D" } %>
+ #
+ # @example Skip a fragment (defaults to 1, skips all following fragments)
+ # <%= f.input :publish_at, :as => :datetime, :discard_minute => true %>
+ # <%= f.input :publish_at, :as => :datetime, :discard_hour => true %>
+ # <%= f.input :publish_at, :as => :datetime, :discard_day => true %>
+ # <%= f.input :publish_at, :as => :datetime, :discard_month => true %>
+ # <%= f.input :publish_at, :as => :datetime, :discard_year => true %>
+ #
+ # @example Change the order
+ # <%= f.input :publish_at, :as => :date, :order => [:month, :day, :year] %>
+ #
+ # @example Include seconds with times (excluded by default)
+ # <%= f.input :publish_at, :as => :time, :include_seconds => true %>
+ #
+ # @example Specify if there should be a blank option at the start of each select or not
+ # <%= f.input :publish_at, :as => :time, :include_blank=> true %>
+ # <%= f.input :publish_at, :as => :time, :include_blank=> false %>
+ #
+ # @todo Document i18n
+ # @todo Check what other Rails options are supported (`start_year`, `end_year`, `use_month_numbers`, `use_short_month`, `add_month_numbers`, `prompt`), write tests for them, and otherwise support them
+ # @todo Could we take the rendering from Rails' helpers and inject better HTML in and around it rather than re-inventing the whee?
+ module Timeish
+
+ def to_html
+ input_wrapping do
+ fragments_wrapping do
+ hidden_fragments <<
+ fragments_label <<
+ template.content_tag(:ol,
+ fragments.map do |fragment|
+ fragment_wrapping do
+ fragment_label_html(fragment) <<
+ fragment_input_html(fragment)
+ end
+ end.join.html_safe, # TODO is this safe?
+ { :class => 'fragments-group' } # TODO refactor to fragments_group_wrapping
+ )
+ end
+ end
+ end
+
+ def fragments
+ date_fragments + time_fragments
+ end
+
+ def time_fragments
+ options[:include_seconds] ? [:hour, :minute, :second] : [:hour, :minute]
+ end
+
+ def date_fragments
+ options[:order] || i18n_date_fragments || default_date_fragments
+ end
+
+ def default_date_fragments
+ [:year, :month, :day]
+ end
+
+ def fragment_wrapping(&block)
+ template.content_tag(:li, template.capture(&block), fragment_wrapping_html_options)
+ end
+
+ def fragment_wrapping_html_options
+ { :class => 'fragment' }
+ end
+
+ def fragment_label(fragment)
+ labels_from_options = options[:labels] || {}
+ if labels_from_options.key?(fragment)
+ labels_from_options[fragment]
+ else
+ ::I18n.t(fragment.to_s, :default => fragment.to_s.humanize, :scope => [:datetime, :prompts])
+ end
+ end
+
+ def fragment_id(fragment)
+ "#{input_html_options[:id]}_#{position(fragment)}i"
+ end
+
+ def fragment_name(fragment)
+ "#{method}(#{position(fragment)}i)"
+ end
+
+ def fragment_label_html(fragment)
+ text = fragment_label(fragment)
+ text.blank? ? "".html_safe : template.content_tag(:label, text, :for => fragment_id(fragment))
+ end
+
+ def value
+ object.send(method) if object && object.respond_to?(method)
+ end
+
+ def fragment_input_html(fragment)
+ opts = input_options.merge(:prefix => fragment_prefix, :field_name => fragment_name(fragment), :default => value, :include_blank => include_blank?)
+ template.send(:"select_#{fragment}", value, opts, input_html_options.merge(:id => fragment_id(fragment)))
+ end
+
+ def fragment_prefix
+ if builder.options.key?(:index)
+ object_name + "[#{builder.options[:index]}]"
+ else
+ object_name
+ end
+ end
+
+ # TODO extract to BlankOptions or similar -- Select uses similar code
+ def include_blank?
+ options.key?(:include_blank) ? options[:include_blank] : builder.include_blank_for_select_by_default
+ end
+
+ def positions
+ { :year => 1, :month => 2, :day => 3, :hour => 4, :minute => 5, :second => 6 }
+ end
+
+ def position(fragment)
+ positions[fragment]
+ end
+
+ def i18n_date_fragments
+ order = ::I18n.t(:order, :scope => [:date])
+ order = nil unless order.is_a?(Array)
+ order
+ end
+
+ def fragments_wrapping(&block)
+ template.content_tag(:fieldset,
+ template.capture(&block).html_safe,
+ fragments_wrapping_html_options
+ )
+ end
+
+ def fragments_wrapping_html_options
+ { :class => "fragments" }
+ end
+
+ def fragments_label
+ if render_label?
+ template.content_tag(:legend,
+ builder.label(method, label_text, :for => "#{input_html_options[:id]}_1i"),
+ :class => "label"
+ )
+ else
+ "".html_safe
+ end
+ end
+
+ def fragments_inner_wrapping(&block)
+ template.content_tag(:ol,
+ template.capture(&block)
+ )
+ end
+
+ def hidden_fragments
+ "".html_safe
+ end
+
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/lib/formtastic/inputs/base/validations.rb b/lib/formtastic/inputs/base/validations.rb
new file mode 100644
index 000000000..014be7de7
--- /dev/null
+++ b/lib/formtastic/inputs/base/validations.rb
@@ -0,0 +1,204 @@
+module Formtastic
+ module Inputs
+ module Base
+ module Validations
+
+ class IndeterminableMinimumAttributeError < ArgumentError
+ def message
+ [
+ "A minimum value can not be determined when the validation uses :greater_than on a :decimal or :float column type.",
+ "Please alter the validation to use :greater_than_or_equal_to, or provide a value for this attribute explicitly with the :min option on input()."
+ ].join("\n")
+ end
+ end
+
+ class IndeterminableMaximumAttributeError < ArgumentError
+ def message
+ [
+ "A maximum value can not be determined when the validation uses :less_than on a :decimal or :float column type.",
+ "Please alter the validation to use :less_than_or_equal_to, or provide a value for this attribute explicitly with the :max option on input()."
+ ].join("\n")
+ end
+ end
+
+ def validations
+ @validations ||= if object && object.class.respond_to?(:validators_on)
+ object.class.validators_on(attributized_method_name).select do |validator|
+ validator_relevant?(validator)
+ end
+ else
+ nil
+ end
+ end
+
+ def validator_relevant?(validator)
+ return true unless validator.options.key?(:if) || validator.options.key?(:unless)
+ conditional = validator.options.key?(:if) ? validator.options[:if] : validator.options[:unless]
+
+ result = if conditional.respond_to?(:call)
+ conditional.call(object)
+ elsif conditional.is_a?(::Symbol) && object.respond_to?(conditional)
+ object.send(conditional)
+ else
+ conditional
+ end
+
+ result = validator.options.key?(:unless) ? !result : !!result
+ not_required_through_negated_validation! if !result && [:presence, :inclusion, :length].include?(validator.kind)
+
+ result
+ end
+
+ def validation_limit
+ validation = validations? && validations.find do |validation|
+ validation.kind == :length
+ end
+ if validation
+ validation.options[:maximum] || (validation.options[:within].present? ? validation.options[:within].max : nil)
+ else
+ nil
+ end
+ end
+
+ # Prefer :greater_than_or_equal_to over :greater_than, for no particular reason.
+ def validation_min
+ validation = validations? && validations.find do |validation|
+ validation.kind == :numericality
+ end
+
+ if validation
+ # We can't determine an appropriate value for :greater_than with a float/decimal column
+ raise IndeterminableMinimumAttributeError if validation.options[:greater_than] && column? && [:float, :decimal].include?(column.type)
+
+ if validation.options[:greater_than_or_equal_to]
+ return (validation.options[:greater_than_or_equal_to].call(object)) if validation.options[:greater_than_or_equal_to].kind_of?(Proc)
+ return (validation.options[:greater_than_or_equal_to])
+ end
+
+ if validation.options[:greater_than]
+ return (validation.options[:greater_than].call(object) + 1) if validation.options[:greater_than].kind_of?(Proc)
+ return (validation.options[:greater_than] + 1)
+ end
+ end
+ end
+
+ # Prefer :less_than_or_equal_to over :less_than, for no particular reason.
+ def validation_max
+ validation = validations? && validations.find do |validation|
+ validation.kind == :numericality
+ end
+ if validation
+ # We can't determine an appropriate value for :greater_than with a float/decimal column
+ raise IndeterminableMaximumAttributeError if validation.options[:less_than] && column? && [:float, :decimal].include?(column.type)
+
+ if validation.options[:less_than_or_equal_to]
+ return (validation.options[:less_than_or_equal_to].call(object)) if validation.options[:less_than_or_equal_to].kind_of?(Proc)
+ return (validation.options[:less_than_or_equal_to])
+ end
+
+ if validation.options[:less_than]
+ return ((validation.options[:less_than].call(object)) - 1) if validation.options[:less_than].kind_of?(Proc)
+ return (validation.options[:less_than] - 1)
+ end
+ end
+ end
+
+ def validation_step
+ validation = validations? && validations.find do |validation|
+ validation.kind == :numericality
+ end
+ if validation
+ validation.options[:step]
+ else
+ nil
+ end
+ end
+
+ def validation_integer_only?
+ validation = validations? && validations.find do |validation|
+ validation.kind == :numericality
+ end
+ if validation
+ validation.options[:only_integer]
+ else
+ false
+ end
+ end
+
+ def validations?
+ validations != nil
+ end
+
+ def required?
+ return false if options[:required] == false
+ return true if options[:required] == true
+ return false if not_required_through_negated_validation?
+ if validations?
+ validations.select { |validator|
+ if validator.options.key?(:on)
+ return false if (validator.options[:on] != :save) && ((object.new_record? && validator.options[:on] != :create) || (!object.new_record? && validator.options[:on] != :update))
+ end
+ case validator.kind
+ when :presence
+ true
+ when :inclusion
+ validator.options[:allow_blank] != true
+ when :length
+ validator.options[:allow_blank] != true &&
+ validator.options[:minimum].to_i > 0 ||
+ validator.options[:within].try(:first).to_i > 0
+ else
+ false
+ end
+ }.any?
+ else
+ return responds_to_global_required? && !!builder.all_fields_required_by_default
+ end
+ end
+
+ def required_attribute?
+ required? && builder.use_required_attribute
+ end
+
+ def not_required_through_negated_validation?
+ @not_required_through_negated_validation
+ end
+
+ def not_required_through_negated_validation!
+ @not_required_through_negated_validation = true
+ end
+
+ def responds_to_global_required?
+ true
+ end
+
+ def optional?
+ !required?
+ end
+
+ def autofocus?
+ opt_autofocus = options[:input_html] && options[:input_html][:autofocus]
+
+ !!opt_autofocus
+ end
+
+ def column_limit
+ if column? && column.respond_to?(:limit)
+ case column.type
+ when :integer
+ (2 ** (column.limit * 8)).to_s.length + 1
+ else
+ column.limit
+ end
+ end
+ end
+
+ def limit
+ validation_limit || column_limit
+ end
+
+ end
+ end
+ end
+end
+
diff --git a/lib/formtastic/inputs/base/wrapping.rb b/lib/formtastic/inputs/base/wrapping.rb
new file mode 100644
index 000000000..be897162e
--- /dev/null
+++ b/lib/formtastic/inputs/base/wrapping.rb
@@ -0,0 +1,47 @@
+module Formtastic
+ module Inputs
+ module Base
+ # @todo relies on `dom_id`, `required?`, `optional`, `errors?`, `association_primary_key` & `sanitized_method_name` methods from another module
+ module Wrapping
+
+ # Override this method if you want to change the display order (for example, rendering the
+ # errors before the body of the input).
+ def input_wrapping(&block)
+ template.content_tag(:li,
+ [template.capture(&block), error_html, hint_html].join("\n").html_safe,
+ wrapper_html_options
+ )
+ end
+
+ def wrapper_html_options
+ opts = (options[:wrapper_html] || {}).dup
+ opts[:class] =
+ case opts[:class]
+ when Array
+ opts[:class].dup
+ when nil
+ []
+ else
+ [opts[:class].to_s]
+ end
+ opts[:class] << as
+ opts[:class] << "input"
+ opts[:class] << "error" if errors?
+ opts[:class] << "optional" if optional?
+ opts[:class] << "required" if required?
+ opts[:class] << "autofocus" if autofocus?
+ opts[:class] = opts[:class].join(' ')
+
+ opts[:id] ||= wrapper_dom_id
+
+ opts
+ end
+
+ def wrapper_dom_id
+ @wrapper_dom_id ||= "#{dom_id.to_s.gsub((association_primary_key || method).to_s, sanitized_method_name.to_s)}_input"
+ end
+
+ end
+ end
+ end
+end
diff --git a/lib/formtastic/inputs/boolean_input.rb b/lib/formtastic/inputs/boolean_input.rb
new file mode 100644
index 000000000..9d8233fef
--- /dev/null
+++ b/lib/formtastic/inputs/boolean_input.rb
@@ -0,0 +1,104 @@
+module Formtastic
+ module Inputs
+ # Boolean inputs are used to render an input for a single checkbox, typically for attributes
+ # with a simple yes/no or true/false value. Boolean inputs are used by default for boolean
+ # database columns.
+ #
+ # @example Full form context and markup
+ # <%= semantic_form_for @post %>
+ # <%= f.inputs do %>
+ # <%= f.input :published, :as => :boolean %>
+ # <% end %>
+ # <% end %>
+ #
+ #
+ #
+ #
+ #
+ #
+ #
+ #
+ # Published?
+ #
+ #
+ #
+ #
+ #