Skip to content
This repository
Fetching contributors…

Cannot retrieve contributors at this time

file 347 lines (250 sloc) 12.975 kb

Formtastic Sneaky Preview

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.

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_id %>
      <%= form.input :publication_state_id, :as => :radio %>
      <%= form.input :author_id, :as => :select %>
      <%= form.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 %>
    <% end %>

    <% form.buttons do %>
      <%= form.commit_button %>
    <% end %>

  <% end %>

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, hacking together enough Ruby to prove it could be done.

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

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

The Available Inputs

  • :select (a select menu for belongs_to associations) – default for columns ending in ‘_id’
  • :radio (a set of radio inputs for belongs_to associations) – alternative for columns ending in ‘_id’
  • :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
  • :boolean_select (a yes/no select box)
  • :string (a text field) – default for :string column types
  • :numeric (a text field, like string) – default for :integer, :float and :decimal column types
  • :file (a file field) – default for paperclip or attachment_fu attributes

The documentation is pretty good for each of these (what it does, what the output is, etc) so go check it out.

Fields without database columns and non-ActiveRecord objects

Formtastic is pretty tightly coupled to ActiveRecord database columns, but it will at least attempt to play nicely with your models other methods (like a virtual column ‘full_name’ which is split into first_name and last_name in a before save callback), and I’d like this to continue and improve.

Configuration

If you wish, put something like this in config/initializers/formtastic_config.rb:

  # Should all fields be considered "required" by default
  # Defaults to true, see ValidationReflection notes below
  Formtastic::SemanticFormBuilder.all_fields_required_by_default = false
  
  # Set the string that will be appended to the labels/fieldsets which are required
  # Default is '<abbr title="required">*</abbr>'
  Formtastic::SemanticFormBuilder.required_string = "(required)"
  
  # Set the string that will be appended to the labels/fieldsets which are optional
  # Defaults to an empty string ("")
  Formtastic::SemanticFormBuilder.optional_string = "(optional)"

  # Set the way inline errors will be displayed.
  # Defaults to :string, valid options are :sentence, :list and :none
  Formtastic::SemanticFormBuilder.inline_errors = :list

  # Set the method to call on label text to transform or format it for human-friendly reading
  # Defaults to :titleize, but you might want :humanize, :to_s, etc
  Formtastic::SemanticFormBuilder.label_str_method = :titleize

  # Set the array of methods to try calling on parent objects in :select and :radio inputs
  # for the text inside each @<option>@ tag or alongside each radio @<input>@.  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 = ["title_and_author", "display_name", "login", "to_s"]

ValidationReflection plugin

If you have the ValidationReflection plugin installed, you won’t have to specify the :required option (it checks the validations on the model instead).

Status

THIS IS DEFINITELY NOT PRODUCTION-READY. THINGS ARE GOING TO CHANGE A BIT BEFORE WE HIT 1.0.

It’s opinionated, incomplete, a work in progress and a bit rough around the edges stiull, but I hope you try it and offer some suggestions and improvements any way.

On the plus side, it has a comprehensive spec suite and contributions from at least ten independent developers.

Roadmap to 1.0

Wishlist on the wiki is serving as pretty good documentation for the roadmap to 1.0 and beyond right now, but I’ll work on getting a real tracking system or something happening soon.

Usage

The smallest example:

  <% semantic_form_for @post do |form| %>
    <% form.inputs do %>
      <%= form.input :title %>
      <%= form.input :body %>
      <%= form.input :section_id, :as => :radio %>
    <% end %>
    <% form.buttons do %>
      <%= form.commit_button %>
    <% end %>
  <% end %>

Yes, it’ll even handle belongs_to associations, rendering a radio list or select box with the right set of choices! The output is something like:

  <form action="/posts" class="formtastic new_post" id="new_post" method="post">
    <fieldset class="inputs">
      <ol>
        <li class="string required" id="post_title_input">
          <label for="post_title">Title<abbr title="required">*</abbr></label>
          <input id="post_title" maxlength="255" name="post[title]" size="50" type="text" />
        </li>
        <li class="text optional" id="post_body_input">
          <label for="post_body">Body</label>
          <textarea cols="40" id="post_body" name="post[body]" rows="20"></textarea>
        </li>
        <li class="radio required" id="post_author_id_input">
          <fieldset>
            <legend><span>Author<abbr title="required">*</abbr></span></legend>
            <ol>
              <li>
                <label for="post_author_id_37">
                  <input id="post_author_id_37" name="post[author_id]" type="radio" value="37" />
                  Fred Smith
                </label>
              </li>
              <li>
                <label for="post_author_id_42">
                  <input id="post_author_id_42" name="post[author_id]" type="radio" value="42" />
                  Bob Rock
                </label>
              </li>
            </ol>
          </fieldset>
        </li>
      </ol>
    </fieldset>
    <fieldset class="buttons">
      <ol>
        <li class="commit">
          <input name="commit" type="submit" value="Create Post" />
        </li>
      </ol>
    </fieldset>
  </form>

If you’re happy to accept the default input types and labels, there’s a shorthand version too:

  <% semantic_form_for @user do |form| %>
    <%= form.inputs :title, :body, :section_id, :created_at %>
    <%= form.buttons :commit %>
  <% end %>

You don’t even have to specify the field list (Formtastic will simply render and input for each column in the database table) or button list (usually you only need one commit button to create/save):

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

Pretty soon we won’t have to write any code at all ;)

Nested forms (Rails 2.3+)

Nested forms are supported:

  <% semantic_form_for @post do |post| %>
    <%= post.semantic_fields_for :author do |author| %>
      <%= author.inputs %>
    <%= end %>
    <%= post.buttons %>
  <% end %>

Internationalization (I18n)

Supports I18n! 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.

Here is an example locale file:

  en:
    formtastic:
      yes: 'Yes'
      no: 'No'
      create: 'Create'
      save: 'Save'
      year: 'Year'
      month: 'Month'
      day: 'Day'
      hour: 'Hour'
      minute: 'Minute'
      second: 'Second'

Extending Formtastic

Adding functionality to Formtastic can be done by extending SemanticFormBuilder and configuring formtastic’s builder.

To create a new form method that displayed a star rating define the new builder by subclassing SemanticFormBuilder:

  # lib/rating_semantic_form_builder.rb
  class RatingBuilder < Formtastic::SemanticFormBuilder
    # new method to be added allowing full control over the label and value
    def rating(stars, options = {})
      options[:label] ||= 'rating'
      content = input_label(options[:label], options)
      content += @template.content_tag(:span, '*' * stars.to_i, :id => "#{@object_name}_#{options[:label]}")
      @template.content_tag(:li, content)
    end
  end

Set Formtastic’s builder to your newly defined RatingBuilder

  1. RAILS_ROOT/config/initializers/override_formtastic_builder.rb
    require ‘rating_semantic_form_builder.rb’
  1. set the formtastic builder to a customized version of the formtastic builder
    Formtastic::SemanticFormHelper.builder = RatingBuilder

Use semantic_form_for just like you would with Formtastic.


<% semantic_form_for Project.new do |form| >
< form.input_field_set :name => “ratings” do >
<= form.rating 5 >
<= form.rating 11, :label => ‘awesomeness (out of 10)’ >
< end >
<= form.buttons >
< end %>

Dependencies

There are none, but…

  • if you have the ValidationReflection plugin is installed, you won’t have to specify the :required option (it checks the validations on the model instead)
  • rspec, rspec_hpricot_matchers and rcov gems (plus any of their own dependencies) are required for the test suite

What about Stylesheets?

A proof-of-concept (very much a work-in-progress) stylesheet is provided which you can include in your layout. Customisation is best achieved by overriding these styles in an additional stylesheet so that the formtastic styles can be updated without clobbering your changes.

1. Use the generator to copy the formtastic.css and formtastic_changes.css into your public directory

./script/generate formtastic_stylesheets

2. Add both formtastic.css and formtastic_changes.css to your layout:

<%= stylesheet_link_tag "formtastic" %>
<%= stylesheet_link_tag "formtastic_changes" %>

Compatibility

I’m only testing Formtastic with the latest Rails 2.2.x stable release, and it should be fine under Rails 2.3 as well (including nested forms).

But it doesn’t do that thing I really need!

It might not ever do it either, there are no silver bullets. I want to make the usual stuff easy, and the unusual stuff possible. That might mean that some of the inputs on your form still have to be hard-coded, but some is better than all, right?

Many thanks to Formtastic’s contributors

Project Info

Formtastic is hosted on Github: http://github.com/justinfrench/formtastic/, where your contributions, forkings, comments and feedback are greatly welcomed.

Copyright © 2007-2008 Justin French, released under the MIT license.

Something went wrong with that request. Please try again.