Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

HTML forms library for ruby

tree: 7bdcb11552

Fetching latest commit…

Cannot retrieve the latest commit at this time

README.rdoc

Forme

Forme is a HTML forms library for ruby with the following goals:

  1. Have no external dependencies

  2. Have a simple API

  3. Support forms both with and without related objects

  4. Allow compiling down to different types of output

Demo Site

A demo site is available at forme.heroku.com

Source Code

Source code is available on GitHub at github.com/jeremyevans/forme

Basic Usage

Without an object, Forme is a simple form builder:

f = Forme::Form.new
f.open(:action=>'/foo', :method=>:post) # '<form action="/foo" method="post">'
f.input(:textarea, :value=>'foo', :name=>'bar') # '<textarea name="bar">foo</textarea>'
f.input(:text, :value=>'foo', :name=>'bar') # '<input name="bar" type="text" value="foo"/>'
f.close # '</form>'

With an object, Form#input calls forme_input on the obj with the form, field, and options, which should return a Forme::Input or Forme::Tag instance. Also, in Form#initialize, forme_config is called on object with the form if the object responds to it, allowing customization of the entire form based on the object.

f = Forme::Form.new(obj)
f.input(:field) # '<input id="obj_field" name="obj[field]" type="text" value="foo"/>'

If the object doesn't respond to forme_input, it falls back to creating text fields with the name and id set to the field name and the value set by calling the given method on the object.

f = Forme::Form.new([:foo])
f.input(:first) # '<input id="first" name="first" type="text" value="foo"/>'

DSL

Forme comes with a DSL:

Forme.form(:action=>'/foo') do |f|
  f.input(:text, :name=>'bar')
  f.tag(:fieldset) do
    f.input(:textarea, :name=>'baz')
  end
end
# <form action="/foo">
#   <input name="bar" type="text"/>
#   <fieldset>
#     <textarea name="baz"></textarea>
#   </fieldset>
# </form>

You can wrap up multiple inputs with the :inputs method:

Forme.form(:action=>'/foo') do |f|
  f.inputs([[:text, {:name=>'bar'}], [:textarea, {:name=>'baz'}]])
end
# <form action="/foo">
#   <fieldset class="inputs">
#     <input name="bar" type="text"/>
#     <textarea name="baz"></textarea>
#   </fieldset>
# </form>

You can even do everything in a single method call:

Forme.form({:action=>'/foo'},
  :inputs=>[[:text, {:name=>'bar'}], [:textarea, {:name=>'baz'}]])

Basic Design

Interally, Forme builds an abstract syntax tree of objects that represent the form. The abstract syntax tree goes through a series of transformations that convert it from high level abstract forms to low level abstract forms and finally to strings. Here are the main classes used by the library:

Forme::Form

main object

Forme::Input

high level abstract tag (a single Input could represent a select box with a bunch of options)

Forme::Tag

low level abstract tag representing an html tag (there would be a separate Tag for each option in a select box)

The group of objects that perform the transformations to the abstract syntax trees are known as transformers. Transformers use a functional style, and all use a call-based API, so you can use a Proc for any custom transformer.

Transformer Types

serializer

tags input/tag, returns string

formatter

takes input, returns tag

error_handler

takes tag and input, returns version of tag with errors noted

labeler

takes tag and input, returns labeled version of tag

wrapper

takes tag and input, returns wrapped version of tag

inputs_wrapper

takes form, options hash, and block, wrapping block in a tag

The serializer is the base of the transformations. It turns Tag instances into strings. If it comes across an Input, it calls the formatter on the Input to turn it into a Tag, and then serializes that Tag. The formatter first converts the Input to a Tag, and then calls the error_handler if the :error option is set and the labeler if the :label option is set. Finally, it calls the wrapper to wrap the resulting tag before returning it.

The inputs_wrapper is called by Forme::Form#inputs and serves to wrap a bunch of related inputs.

Built-in Transformers

Forme ships with a bunch of built-in transformers that you can use:

serializer

:default

returns HTML strings

:html_usa

returns HTML strings, formats dates and times in American format without timezones

:text

returns plain text strings

formatter

:default

turns Inputs into Tags

:disabled

disables all resulting input tags

:readonly

uses span tags for most values, good for printable versions of forms

error_handler

:default

modifies tag to add an error class and adds a span with the error message

labeler

:default

uses implicit labels, where the tag is a child of the label tag

:explicit

uses explicit labels with the for attribute, where tag is a sibling of the label tag

wrapper

:default

returns tag without wrapping

:li

wraps tag in li tag

:p

wraps tag in p tag

:div

wraps tag in div tag

:span

wraps tag in span tag

:trtd

wraps tag in a tr tag with a td for the label and a td for the tag, useful for lining up inputs with the :explicit labeler without CSS

inputs_wrapper

:default

uses a fieldset to wrap inputs

:ol

uses an ol tag to wrap inputs, useful with :li wrapper

:div

uses a div tag to wrap inputs

:fieldset_ol

use both a fieldset and an ol tag to wrap inputs

:table

uses a table tag to wrap inputs, useful with :trtd wrapper

Configurations

You can associate a group of transformers into a configuration. This allows you to specify a single :config option when creating a Form and have it automatically set all the related transformers.

There are a few configurations supported by default:

:default

All default transformers

:formtastic

fieldset_ol inputs_wrapper, li wrapper, explicit labeler

You can register and use your own configurations easily:

Forme.register_config(:mine, :wrapper=>:li, :inputs_wrapper=>:ol, :serializer=>:html_usa)
Forme::Form.new(:config=>:mine)

If you want to, you can base your configuration on an existing configuration:

Forme.register_config(:yours, :base=>:mine, :inputs_wrapper=>:fieldset_ol)

You can mark a configuration as the default using:

Forme.default_config = :mine

Sequel Support

Forme ships with a Sequel plugin (use Sequel::Model.plugin :forme to enable), that makes Sequel::Model instances support the forme_config and forme_input methods and return customized inputs.

It deals with inputs based on database columns, virtual columns, and associations. It also handles nested associations using the subform method:

Forme.form(Album[1], :action=>'/foo') do |f|
  f.inputs([:name, :copies_sold, :tags]) do
    f.subform(:artist, :inputs=>[:name])
    f.subform(:tracks, :inputs=>[:number, :name])
  end
end

For many_to_one associations, you can use the :as=>:radio option to use a series of radio buttons, and for one_to_many and many_to_many associations, you can use the :as=>:checkbox option to use a series of checkboxes. For one_to_many and many_to_many associations, you will probably want to use the association_pks plugin that ships with Sequel.

The Forme Sequel plugin also integerates with Sequel's validation reflection support with the validation_class_methods plugin that ships with Sequel. It will add pattern and maxlength attributes based on the format, numericality, and length validations.

Sinatra ERB Support

Forme ships with a Sinatra extension that you can get by require "forme/sinatra" and using helpers Forme::Sinatra::ERB in your Sinatra::Base subclass. It allows you to use the following API in your Sinatra ERB forms:

<% form(@obj, :action=>'/foo') do |f| %>
  <%= f.input(:field) %>
  <% f.tag(:fieldset) do %>
    <%= f.input(:field_two) %>
  <% end %>
<% end %>

In addition to ERB, it also works with Sinatra's Erubis support.

Other Similar Projects

All of these have external dependencies:

  1. Rails built-in helpers

  2. Formtastic

  3. simple_form

  4. padrino-helpers

Forme's API draws a lot of inspiration from both Formtastic and simple_form.

Author

Jeremy Evans <code@jeremyevans.net>

Something went wrong with that request. Please try again.