Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Renamed input_field_set() to just inputs() and added a shorthand vers…

…ion where you pass in a list of fields, rather than a block:

<% semantic_form_for @post do |form| %>
  <%= form.inputs :title, :body %>
<% end %>

Or you can skip the field list too, and get an input for every column in the database table (like scaffolding, great for slapping together a quick form during development):

<% semantic_form_for @post do |form| %>
  <%= form.inputs %>
<% end %>

* input_field_set() is aliased to inputs() for backwards compatibility for now
* heaps of documentation
* README examples updated too

See the inputs() documentation for the lowdown, pretty happy with this.
  • Loading branch information...
commit 0b370bae143325029f59e94d381f08857ff3285c 1 parent a9a1f3b
Justin French authored
Showing with 269 additions and 73 deletions.
  1. +21 −9 README.textile
  2. +106 −29 lib/justin_french/formtastic.rb
  3. +142 −35 spec/formtastic_spec.rb
30 README.textile
View
@@ -9,7 +9,7 @@ Hacked together forms were easy, but awesome forms with decent semantics, rich m
<pre>
<% semantic_form_for @article do |form| %>
- <% form.input_field_set :name => "Basic" do %>
+ <% form.inputs :name => "Basic" do %>
<%= form.input :title %>
<%= form.input :body %>
<%= form.input :section_id %>
@@ -18,7 +18,7 @@ Hacked together forms were easy, but awesome forms with decent semantics, rich m
<%= form.input :allow_comments, :label => "Allow commenting on this article" %>
<% end %>
- <% form.input_field_set :name => "Advanced" do %>
+ <% 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 %>
@@ -75,7 +75,6 @@ The documentation is pretty good for each of these (what it does, what the outpu
h3. One day...
* calendars and other more humanized date/time selections
-* -vanilla html file selection to work well with things like ThoughtBot's "Paperclip":http://www.thoughtbot.com/projects/paperclip-
* flash-based multi-file selection
* an interface for tagging and other has_many_through style associations
@@ -100,7 +99,7 @@ If you wish, put something like this in config/initializers/formtastic_config.rb
h2. Status
-*THIS IS DEFINITELY NOT PRODUCTION-READY. THINGS ARE GOING TO CHANGE A LOT.*
+*THIS IS DEFINITELY NOT PRODUCTION-READY. THINGS ARE GOING TO CHANGE A BIT.*
It's incredibly opinionated, incomplete, a work in progress, messy around the edges, messy in the middle too, tightly coupled to the database, tightly coupled to "my way" of doing things and has an incomplete (but fast-growing) test suite, but I hope you try it and offer some suggestions and improvements any way.
@@ -109,10 +108,7 @@ h2. Roadmap to 1.0
* there's a few of TODOs left in the code
* improve the generated sample stylesheets
-* add a shortcut method like <code><%= form.inputs :name, :login, :email, :bio %></code> for those that want the form with zero configuration
-* reconsider the naming of button_field_set and input_field_set
* take a look at a DSL for the validation errors heading and messages typically placed at the top of a form
-* loop through a few methods like to_s and name on ActiveRecord objects (in addition to the existing to_label) so that the belongs_to associations "just work" a little more often, rather than depending on to_label
* maybe get rid of the JustinFrench:: module namespace
h2. Usage
@@ -121,7 +117,7 @@ The smallest example:
<pre>
<% semantic_form_for @user do |form| %>
- <% form.input_field_set do %>
+ <% form.inputs do %>
<%= form.input :name %>
<%= form.input :email %>
<%= form.input :password %>
@@ -153,10 +149,26 @@ With an output something like:
</form>
</pre>
+If you're happy to accept the default input types and labels, there's a shorthand version too:
+
+<pre>
+ <% semantic_form_for @user do |form| %>
+ <%= form.inputs :name, :email, :password, :department_id, :date_of_birth %>
+ <% end %>
+</pre>
+
+You don't even have to specify the field list (Formtastic will simply render and input for each column in the database table):
+
+<pre>
+ <% semantic_form_for @user do |form| %>
+ <%= form.inputs %>
+ <% end %>
+</pre>
+
h2. Conventions & Prerequisites
-In a few places (like radio or select widgets for belongs_to associations) Formtastic expects your ActiveRecord instances to respond to the <code>to_label</code> method (returning a String). You can easily add this to your models, for example, a User object might want to return the user's first name, last name and login:
+In a few places (like radio or select widgets for belongs_to associations) Formtastic expects your ActiveRecord instances to respond to the <code>to_label</code> or <code>to_s</code> method (returning a String). You can easily add this to your models: For example, a User object might want to return the user's first name, last name and login:
<pre>
class User < ActiveRecord::Base
135 lib/justin_french/formtastic.rb
View
@@ -97,7 +97,7 @@ class SemanticFormBuilder < ActionView::Helpers::FormBuilder
# Example:
#
# <% semantic_form_for @employee do |form| %>
- # <% form.input_field_set do -%>
+ # <% form.inputs do -%>
# <%= form.input :name, :label => "Full Name"%>
# <%= form.input :manager_id, :as => :radio %>
# <%= form.input :hired_at, :as => :date, :label => "Date Hired" %>
@@ -128,46 +128,115 @@ def input(method, options = {})
return template.content_tag(:li, list_item_content, { :id => html_id, :class => html_class })
end
-
- # Creates a fieldset and ol tag wrapping for form inputs as list items. Example:
- #
- # <% form_for @user do |form| %>
- # <% form.input_field_set do %>
- # <li>form input 1</li>
- # <li>form input 2</li>
+
+ # 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 %>
#
- # Output:
+ # # With a list of fields:
+ # <% semantic_form_for @post do |form| %>
+ # <%= form.inputs :title, :body %>
+ # <% end %>
+ #
+ # # Output:
# <form ...>
# <fieldset class="inputs">
# <ol>
- # <li>form input 1</li>
- # <li>form input 2</li>
+ # <li class="string">...</li>
+ # <li class="text">...</li>
# </ol>
# </fieldset>
# </form>
- #
- # HTML attributes for the fieldset can be passed in as a hash before the block, with the class
- # set to "inputs" by default. Example:
- # <% input_field_set :id => "main-inputs" do %>
- # ...
+ #
+ # === 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 %>
#
- # One special option exists (:name), which is passed along to a legend tag within the
- # fieldset (otherwise a legend is not generated):
+ # === Options
#
- # <% input_field_set :name => "Advanced Options" do %>...<% end %>
- def input_field_set(field_set_html_options = {}, &block)
- field_set_html_options[:class] ||= "inputs"
- field_set_and_list_wrapping(field_set_html_options, &block)
+ # All options (with the exception of :name) are passed down to the fieldset as HTML
+ # attributes (id, class, style, etc). If provided, the :name option is passed into a
+ # legend tag inside the fieldset (otherwise a legend is not generated).
+ #
+ # # 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 %>
+ #
+ # === 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 %>
+ # <% end %>
+ #
+ # # Output:
+ # <form ...>
+ # <fieldset class="inputs">
+ # <ol>
+ # <li class="string">...</li>
+ # <li class="text">...</li>
+ # </ol>
+ # </fieldset>
+ # <fieldset class="inputs" id="advanced">
+ # <legend><span>Advanced</span></legend>
+ # <ol>
+ # <li class="datetime">...</li>
+ # <li class="select">...</li>
+ # </ol>
+ # </fieldset>
+ # </form>
+ def inputs(*args, &block)
+ if block_given?
+ html_options = args.first || {}
+ html_options[:class] ||= "inputs"
+ field_set_and_list_wrapping(html_options, &block)
+ else
+ html_options = args.last.is_a?(Hash) ? args.pop : {}
+ html_options[:class] ||= "inputs"
+ args = @object.class.column_names if args.empty?
+ contents = args.map { |method| input(method.to_sym) }
+ field_set_and_list_wrapping(html_options, contents)
+ end
end
+ alias_method :input_field_set, :inputs
# Creates a fieldset and ol tag wrapping for form buttons / actions as list items.
- # See input_field_set documentation for a full example. The fieldset's default class attriute
+ # See inputs documentation for a full example. The fieldset's default class attriute
# is set to "buttons".
#
- # See input_field_set for html attriutes and special options.
+ # See inputs for html attributes and special options.
def button_field_set(field_set_html_options = {}, &block)
field_set_html_options[:class] ||= "buttons"
field_set_and_list_wrapping(field_set_html_options, &block)
@@ -551,16 +620,24 @@ def required_or_optional_string(required) #:nodoc:
required ? @@required_string : @@optional_string
end
- def field_set_and_list_wrapping(field_set_html_options, &block) #:nodoc:
+ def field_set_and_list_wrapping(field_set_html_options, contents = '', &block) #:nodoc:
legend_text = field_set_html_options.delete(:name)
legend = legend_text.blank? ? "" : template.content_tag(:legend, template.content_tag(:span, legend_text))
-
- template.concat(
+ if block_given?
+ contents = template.capture(&block)
+ template.concat(
+ template.content_tag(:fieldset,
+ legend + template.content_tag(:ol, contents),
+ field_set_html_options
+ )
+ )
+ else
template.content_tag(:fieldset,
- legend + template.content_tag(:ol, template.capture(&block)),
+ legend + template.content_tag(:ol, contents),
field_set_html_options
)
- )
+ end
+
end
# For methods that have a database column, take a best guess as to what the inout method
177 spec/formtastic_spec.rb
View
@@ -1287,58 +1287,165 @@ class Author; end
end
- describe '#input_field_set' do
+ describe '#inputs' do
- describe 'when no options are provided' do
- before do
- semantic_form_for(@new_post) do |builder|
- builder.input_field_set do
- concat('hello')
+ describe 'when a block is passed' do
+
+ describe 'when no options are provided' do
+ before do
+ semantic_form_for(@new_post) do |builder|
+ builder.inputs do
+ concat('hello')
+ end
end
end
+ it 'should render a fieldset inside the form, with a class of "inputs"' do
+ output_buffer.should have_tag("form fieldset.inputs")
+ end
+ it 'should render an ol inside the fieldset' do
+ output_buffer.should have_tag("form fieldset.inputs ol")
+ end
+ it 'should render the contents of the block inside the ol' do
+ output_buffer.should have_tag("form fieldset.inputs ol", /hello/)
+ end
+ it 'should not render a legend inside the fieldset' do
+ output_buffer.should_not have_tag("form fieldset.inputs legend")
+ end
end
- it 'should render a fieldset inside the form, with a class of "inputs"' do
- output_buffer.should have_tag("form fieldset.inputs")
- end
- it 'should render an ol inside the fieldset' do
- output_buffer.should have_tag("form fieldset.inputs ol")
- end
- it 'should render the contents of the block inside the ol' do
- output_buffer.should have_tag("form fieldset.inputs ol", /hello/)
+
+ describe 'when a :name option is provided' do
+ before do
+ @legend_text = "Advanced options"
+
+ semantic_form_for(@new_post) do |builder|
+ builder.inputs :name => @legend_text do
+ end
+ end
+ end
+ it 'should render a fieldset inside the form' do
+ output_buffer.should have_tag("form fieldset legend", /#{@legend_text}/)
+ end
end
- it 'should not render a legend inside the fieldset' do
- output_buffer.should_not have_tag("form fieldset.inputs legend")
+
+ describe 'when other options are provided' do
+ before do
+ @id_option = 'advanced'
+ @class_option = 'wide'
+
+ semantic_form_for(@new_post) do |builder|
+ builder.inputs :id => @id_option, :class => @class_option do
+ end
+ end
+ end
+ it 'should pass the options into the fieldset tag as attributes' do
+ output_buffer.should have_tag("form fieldset##{@id_option}")
+ output_buffer.should have_tag("form fieldset.#{@class_option}")
+ end
end
+
end
- describe 'when a :name option is provided' do
+ describe 'without a block' do
+
before do
- @legend_text = "Advanced options"
+ Post.stub!(:column_names).and_return(["title", "body", "created_at", "author_id"])
+ Author.stub!(:find).and_return([@fred, @bob])
- semantic_form_for(@new_post) do |builder|
- builder.input_field_set :name => @legend_text do
+ @new_post.stub!(:title)
+ @new_post.stub!(:body)
+ @new_post.stub!(:created_at)
+ @new_post.stub!(:author_id)
+
+ @new_post.stub!(:column_for_attribute).with(:title).and_return(mock('column', :type => :string, :limit => 255))
+ @new_post.stub!(:column_for_attribute).with(:body).and_return(mock('column', :type => :text))
+ @new_post.stub!(:column_for_attribute).with(:created_at).and_return(mock('column', :type => :datetime))
+ @new_post.stub!(:column_for_attribute).with(:author_id).and_return(mock('column', :type => :integer, :limit => 4))
+ end
+
+ describe 'with no args' do
+
+ before do
+ semantic_form_for(@new_post) do |builder|
+ concat(builder.inputs)
end
end
- end
- it 'should render a fieldset inside the form' do
- output_buffer.should have_tag("form fieldset legend", /#{@legend_text}/)
- end
- end
-
- describe 'when other options are provided' do
- before do
- @id_option = 'advanced'
- @class_option = 'wide'
- semantic_form_for(@new_post) do |builder|
- builder.input_field_set :id => @id_option, :class => @class_option do
+ it 'should render a form' do
+ output_buffer.should have_tag('form')
+ end
+
+ it 'should render a fieldset inside the form' do
+ output_buffer.should have_tag('form > fieldset.inputs')
+ end
+
+ it 'should not render a legend in the fieldset' do
+ output_buffer.should_not have_tag('form > fieldset.inputs > legend')
+ end
+
+ it 'should render an ol in the fieldset' do
+ output_buffer.should have_tag('form > fieldset.inputs > ol')
+ end
+
+ it 'should render a list item in the ol for each column returned by Post.column_names' do
+ output_buffer.should have_tag('form > fieldset.inputs > ol > li', :count => Post.column_names.size)
+ end
+
+ it 'should render a string list item for title' do
+ output_buffer.should have_tag('form > fieldset.inputs > ol > li.string')
+ end
+
+ it 'should render a text list item for body' do
+ output_buffer.should have_tag('form > fieldset.inputs > ol > li.text')
+ end
+
+ it 'should render a datetime list item for created_at' do
+ output_buffer.should have_tag('form > fieldset.inputs > ol > li.datetime')
+ end
+
+ it 'should render a select list item for author_id' do
+ output_buffer.should have_tag('form > fieldset.inputs > ol > li.select')
+ end
+
+ end
+
+ describe 'with column names as args' do
+
+ before do
+ semantic_form_for(@new_post) do |builder|
+ concat(builder.inputs(:title, :body))
end
end
+
+ it 'should render a form with a fieldset containing two list items' do
+ output_buffer.should have_tag('form > fieldset.inputs > ol > li', :count => 2)
+ output_buffer.should have_tag('form > fieldset.inputs > ol > li.string')
+ output_buffer.should have_tag('form > fieldset.inputs > ol > li.text')
+ end
+
end
- it 'should pass the options into the fieldset tag as attributes' do
- output_buffer.should have_tag("form fieldset##{@id_option}")
- output_buffer.should have_tag("form fieldset.#{@class_option}")
+
+ describe 'with column names and an options hash as args' do
+
+ before do
+ semantic_form_for(@new_post) do |builder|
+ concat(builder.inputs(:title, :body, :name => "Legendary Legend Text", :id => "my-id"))
+ end
+ end
+
+ it 'should render a form with a fieldset containing two list items' do
+ output_buffer.should have_tag('form > fieldset.inputs > ol > li', :count => 2)
+ end
+
+ it 'should pass the options down to the fieldset' do
+ output_buffer.should have_tag('form > fieldset#my-id.inputs')
+ end
+
+ it 'should use the special :name option as a text for the legend tag' do
+ output_buffer.should have_tag('form > fieldset#my-id.inputs > legend', /Legendary Legend Text/)
+ end
+
end
+
end
end

1 comment on commit 0b370ba

Gareth Townsend

Awesome! But you knew that already.

Please sign in to comment.
Something went wrong with that request. Please try again.