Forme is a HTML forms library for ruby with the following goals:
-
Have no external dependencies
-
Have a simple API
-
Support forms both with and without related objects
-
Allow compiling down to different types of output
A demo site is available at forme.heroku.com
Source code is available on GitHub at github.com/jeremyevans/forme
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"/>'
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'}]])
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.
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.
Forme ships with a bunch of built-in transformers that you can use:
- :default
-
returns HTML strings
- :html_usa
-
returns HTML strings, formats dates and times in American format without timezones
- :text
-
returns plain text strings
- :default
-
turns Inputs into Tags
- :disabled
-
disables all resulting input tags
- :readonly
-
uses
span
tags for most values, good for printable versions of forms
- :default
-
modifies tag to add an error class and adds a span with the error message
- :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
- :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
- :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
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
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.
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.
All of these have external dependencies:
-
Rails built-in helpers
-
Formtastic
-
simple_form
-
padrino-helpers
Forme’s API draws a lot of inspiration from both Formtastic and simple_form.
Jeremy Evans <code@jeremyevans.net>