Forme
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
Installation
sudo gem install forme
Demo Site
A demo site is available at forme-demo.jeremyevans.net
Documentation Site
RDoc Documentation is available at forme.jeremyevans.net
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
Internally, 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
Inputcould represent a select box with a bunch of options) Forme::Tag-
low level abstract tag representing an html tag (there would be a separate
Tagfor 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
spantags 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
defaulttransformers - :formtastic
-
fieldset_olinputs_wrapper,liwrapper,explicitlabeler
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 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 %>
This example is for ERB/Erubis. Other Sinatra template libraries work differently and probably do not support this integration.
Rails Support
Forme ships with a Rails extension that you can get by require "forme/rails" and using helpers Forme::Rails::ERB in your controller. If allows you to use the following API in your Rails forms:
<%= forme(@obj, :action=>'/foo') do |f| %>
<%= f.input(:field) %>
<%= f.tag(:fieldset) do %>
<%= f.input(:field_two) %>
<% end %>
<% end %>
This has been tested on Rails 3.2, but should hopefully work on Rails 3.0+.
Other Similar Projects
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.
Author
Jeremy Evans <[email protected]>