= 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
5. Integrate easily into web frameworks

= Introduction

Forme is designed to make creating HTML forms easier.  Flexibility and
ease of use are the primary objectives.  Here's a basic example,
showing usage without a related object:

  Forme.form({:action=>'/foo'}) do |f|
    f.input(:text, :name=>'bar')
    f.tag(:fieldset) do
      f.input(:textarea, :name=>'baz')
    end
   f.button('Update')
  end

This results in the following HTML:

  <form action="/foo">
    <input name="bar" type="text">
    <fieldset>
      <textarea name="baz"></textarea>
    </fieldset>
    <input type="submit" value="Update">
  </form>

Forme also supports forms that are associated with objects, and
has specific support for Sequel::Model objects to allow easily building
forms for such objects.  The Sequel support handles inputs based on
database columns, and automatically handles labels and errors:

  Forme.form(Album[1], action: '/foo') do |f|
    f.input :name
    f.input :copies_sold
  end

This results in the following HTML:

  <form action="/foo" method="post">
    <label>Name:
      <input id="album_name" name="album[name]" type="text" value="Rising Force">
    </label>
    <label>Copies Sold:
      <input id="album_copies_sold" inputmode="numeric" name="album[copies_sold]" pattern="-?[0-9]*" type="text" value="100000">
    </label>
  </form>

In addition to integrating with Sequel, Forme also integrates into
three separate web frameworks, Roda, Rails, and Sinatra, allowing
use of forms inside templates.  This is the most common usage of Forme.

One distinct advantage of Forme over other form libraries is the use
of an abstract syntax tree internally, allowing the same form code to
compile to different HTML with different options.  For example, it
allows using the exactly same form code to display a form you can modify
as well as a read-only view, just by passing a single option when
creating the form.  For example, with the first example in this section,
if you pass the <tt>formatter: :readonly</tt> option, you get the
following HTML instead:

  <form action="/foo">
    <span class="readonly-text"></span>
    <fieldset>
      <div class="readonly-textarea"></div>
    </fieldset>
  </form>

This allows you to reuse the same form code in multiple contexts,
which can save considerable development time.

= Installation

  gem install forme

= Links

Demo Site :: http://forme-demo.jeremyevans.net
RDoc :: http://forme.jeremyevans.net
Source :: https://github.com/jeremyevans/forme
Discussion Forum :: https://github.com/jeremyevans/forme/discussions
Bug Tracker :: https://github.com/jeremyevans/forme/issues

= Direct Instantiation

While not typically done, you can instantiate Forme::Form objects directly and use them:

  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, <tt>Form#input</tt> calls +forme_input+ on the obj with the form, field, and options, which
should return a <tt>Forme::Input</tt> or <tt>Forme::Tag</tt> instance.  Also, in <tt>Form#initialize</tt>,
+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 (or using #[] if the object is a hash).

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

= Forme::Form Creation

+Forme.form+ takes up to 3 arguments, and yields the Forme::Form
object to the block (if given).  Here are the argument styles that you can
use for +Forme.form+.

No args :: Creates a +Form+ object with no options and not associated
           to an +obj+, and with no attributes in the opening tag.
1 hash arg :: Treated as opening form tag attributes, creating a
              +Form+ object with no options.
1 non-hash arg :: Treated as the +Form+'s +obj+, with empty options
                  and no attributes in the opening tag.
2 hash args :: First hash is opening attributes, second hash is +Form+
               options.
1 non-hash arg, 1-2 hash args :: First argument is +Form+'s obj, second is
                                 opening attributes, third if provided is
                                 +Form+'s options.

Examples:

  # No arguments
  Forme.form 

  # 1 hash argument (attributes)
  Forme.form(action: '/foo')

  # 1 non-hash argument (a reference object used when building the form)
  Forme.form(Album[1])

  # 2 hash arguments (attributes, and options)
  Forme.form({action: '/foo'}, values: params)

  # 1 non-hash argument, 1-2 hash arguments (ref obj, attributes, options)
  Forme.form(Album[1], action: '/foo')
  Forme.form(Album[1], {action: '/foo'}, values: params)

If you want a Forme::Form instance where the reference object is a Hash, then you need to pass the hash object using the :obj option:

  Forme.form({action: '/foo'}, obj: {foo: 'bar'})

You can also create Forme::Form objects the normal ruby way using Forme::Form#new.  The
difference between Forme::Form#new and Forme.form is that Forme.form includes the enclosing
<form> tag, where Forme::Form#new does not.  Because of this, Forme::Form does not accept
a hash of <form> tag attributes, so it has the following API:

  # No arguments
  Forme::Form.new

  # 1 hash argument 
  Forme::Form.new(values: params)

  # 1 non-hash argument 
  Forme::Form.new(Album[1])

  # 1 non-hash argument, 1-2 hash arguments
  Forme::Form.new(Album[1], values: params)

= Forme::Form Methods

== form

If you create a Form via Forme::Forme#new, you can use the form method to create a form tag:

  f = Forme::Form.new
  f.form(action: '/foo')

This is what Forme.form uses internally to create the +<form>+ tag

== input

This adds an input to the form.  If the form has an associated object, and that
object responds to +forme_input+, calls forme_input with the argument and options:

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

If the form has an associated object, and that object does not respond to +forme_input+,
calls the method on the object (or uses [] if the object is a hash), and uses the result
as the value for a text input:

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

If the object does not respond to +forme_input+, you can change the type of the input
via the +:type+ option:

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

If the form does not have an associated object, the first argument is used as the input type:

  f = Forme::Form.new
  f.input(:text) # '<input type="text" >'
  
The second argument is an options hash.  See below for the supported input types and options.

== tag

This adds a tag to the form.  If a block is given, yields to the block, and tags and inputs
inside the block are placed inside the tag. The first argument is the type of tag to create,
and the second argument if given should be a hash of tag attributes. This allows you to nest
inputs inside tags:

  Forme.form do |f|
    f.tag(:span, :class=>"foo") do
      f.input(:text)
    end
  end

Which results in a form similar to the following:

  <form>
    <span class="foo">
      <input type="text">
    </span>
  </form>

== inputs

This wraps multiple inputs in a tag (it uses the +:inputs_wrapper+ transformer discussed below,
so it uses a +fieldset+ by default). You can give the inputs to add as an enumerable argument:

  f.inputs([:textarea, [:text, :value=>'a']])
  # <fieldset>
  #   <textarea></textarea>
  #   <input type="text" value="a">
  # </fieldset>

You can also provide a block:

  f.inputs([:textarea]) do
    f.input(:text, :value=>'a')
  end

Any options given are passed to the +inputs_wrapper+ (so you can use options such as +:legend+
to set a legend for the fieldset), and also to the +with_opts+ method (so you can use options
such as +:wrapper+ to modify the default wrapper transformer for inputs inside the block).
There is also one option specific to the inputs method:

:nested_inputs_wrapper :: Sets the default inputs_wrapper to use for calls to inputs inside
                          the block.  The reason for this option is that +:inputs_wrapper+
                          option affects the current call to inputs, so if you want to
                          use a different inputs_wrapper for nested calls, you need this option.

== button

This adds a submit input to the form:

  f.button
  # <input type="submit">

It can be called with a string to provide a value for the button:

  f.button('Search')
  # <input type="submit" value="Search">

It can be called with a hash to provide options for the submit input:

  f.button(value: 'Search', class: 'btn')
  # <input class="btn" type="submit" value="Search">

== with_opts

This requires a block, and modifies the Forme::Form's options inside the block,
restoring the options when the block returns:

  f.input(:text)
  # <input type="text">

  f.with_opts(wrapper: :li) do
    f.input(:text)
  end
  # <li><input type="text"></li>

This supports most options you can provide to Forme::Form, but not all.

== with_obj

This uses +with_opts+ to change the Forme::Form object temporarily. It
yields the object to the block, and also supports appending to the
existing namespaces:

  Forme.form([:foo], {action: '/path'}, namespace: 'a') do |f|
    f.input(:first)
    # <input id="a_first" name="a[first]" type="text" value="foo">
    f.with_obj(['foobar'], 'b') do |o|
      f.input(:first, :size=>o.first.size)
      # <input id="a_b_first" name="a[b][first]" size="6" type="text" value="foobar">
    end
  end

== each_obj

This allows you to provide an object-yielding enumerable. +each_object+ will call
+with_obj+ with each object in the enumerable.  It yields each object as well as the
index of the object in the enumerable, and includes the index in the namespace:

  objectlist = [['foobar'], ['good']]
  Forme.form([:foo], :namespace=>'a') do |f|
    f.each_obj(objectlist, 'b') do |o, i|
      f.input(:first, :size=>10+i)
    end
    # <input id="a_b_0_first" name="a[b][0][first]" size="10" type="text" value="foobar">
    # <input id="a_b_1_first" name="a[b][1][first]" size="11" type="text" value="good">
  end

= Sequel Support

Forme ships with a Sequel plugin (use <tt>Sequel::Model.plugin :forme</tt> to enable), that makes
Sequel::Model instances support the +forme_config+ and +forme_input+ methods and return customized inputs.
An additional instance method, +forme_namespace+ can optionally be defined to customize how model classnames
are transformed into form classes and input IDs and names. This can be useful if your Sequel::Model classes
are nested under a parent namespace. The default namespace uses Sequel::Model#underscore.

  module Admin
    class Albums < Sequel::Model
      def forme_namespace
        self.class.name.underscore.tr('/', '_')
      end
    end
  end

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

== Specialized input options for specific column types

In addition to the default Forme options, the Sequel support includes, for specific column types, 
these additional options to the #input method:

=== boolean

:as :: Can be set to :select, :radio, or :checkbox.  :select will use a select input with three
       options, a blank option, a true option, and a false option.  :radio will use two radio
       inputs, one for true and one for false.  :checkbox will use a single checkbox input.
       By default, uses :select if NULL values are allowed and the option is not required, and
       :checkbox otherwise.
:false_label :: The value to use for the false label, 'No' by default.
:false_value :: The value to use for the false input, 'f' by default.
:true_label :: The value to use for the true label, 'Yes' by default.
:true_value :: The value to use for the true input, 't' by default.

=== string

:as :: Can be set to :textarea to use a textarea input. You can use the usual attributes hash or a stylesheet to 
       control the size of the textarea.

== associations

The Sequel support also handles associations, allowing you to change which objects are associated
to the current object.

  Forme.form(Album[1], :action=>'/foo') do |f|
    f.input :name
    f.input :artist
    f.input :tags, :as=>:checkbox
  end

This will create a form similar to:

  <form action="/foo" method="post">
    <label>Name: <input id="album_name" name="album[name]" type="text" value="Blue Hawaii"></label>
    <label>Artist: <select id="album_artist_id" name="album[artist_id]">
      <option selected="selected" value="1">Elvis Presley</option>
      <option value="2">The Beatles</option>
      <option value="3">The Monkeys</option>
    </select></label>
    <span class="label">Tags:
      <label><input checked="checked" id="album_tag_pks_1" name="album[tag_pks][]" type="checkbox" value="1"> Rock and Roll</label>
      <label><input id="album_tag_pks_2" name="album[tag_pks][]" type="checkbox" value="2"> Blues</label>
      <label><input id="album_tag_pks_3" name="album[tag_pks][]" type="checkbox" value="3"> Country</label>
    </span>
  </form>

For one_to_many and many_to_many associations, you will probably want to use the
+association_pks+ plugin that ships with Sequel.

This also supports the pg_array_to_many association type that comes with Sequel's
+pg_array_association+ plugin.

association input options:

:as :: For many_to_one associations, set to :radio to use a series of radio buttons instead
       a select input.  For one_to_many, many_to_many, and pg_array_to_many associations, set
       to :checkbox to use a series of checkboxes instead of a multiple select input.
:dataset :: If a Dataset, uses the dataset to retrieve the options.  If a Proc or Method,
            calls the proc or method with the default dataset, and should return a modified
            dataset to use.
:options :: Specify the options to use for the input(s), instead of querying the database.
:name_method :: If a String or Symbol, treats it as a method name and calls it on each object
                returned by the dataset to get the text to use for the option. If not given,
                tries the following method names in order: :forme_name, :name, :title, :number.
                If given and not a String or Symbol, a callable object is assumed, and the value
                is called with each object and should return the text to use for the option.

== subform

The Sequel support includes a method called subform, which can handle nested_attributes:

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

This adds an input for editing the artist's name after the album's inputs, as well as
inputs for editing the number and name for all of the tracks in the album, creating a
form similar to:

  <form action="/foo" method="post">
  
    <label>Name: <input id="album_name" name="album[name]" type="text" value="Blue Hawaii"></label>
  
    <input id="album_artist_attributes_id" name="album[artist_attributes][id]" type="hidden" value="1">
    <fieldset class="inputs"><legend>Artist</legend>
      <label>Name: <input id="album_artist_attributes_name" name="album[artist_attributes][name]" type="text" value="Elvis Presley"></label>
    </fieldset>
  
    <input id="album_tracks_attributes_0_id" name="album[tracks_attributes][0][id]" type="hidden" value="1">
    <fieldset class="inputs"><legend>Track #1</legend>
      <label>Number: <input id="album_tracks_attributes_0_number" inputmode="numeric" name="album[tracks_attributes][0][number]" pattern="-?[0-9]*" type="text" value="1"></label>
      <label>Name: <input id="album_tracks_attributes_0_name" name="album[tracks_attributes][0][name]" type="text" value="Blue Hawaii"></label>
    </fieldset>
    <input id="album_tracks_attributes_1_id" name="album[tracks_attributes][1][id]" type="hidden" value="2">
    <fieldset class="inputs"><legend>Track #2</legend>
      <label>Number: <input id="album_tracks_attributes_1_number" inputmode="numeric" name="album[tracks_attributes][1][number]" pattern="-?[0-9]*" type="text" value="2"></label>
      <label>Name: <input id="album_tracks_attributes_1_name" name="album[tracks_attributes][1][name]" type="text" value="Almost Always True"></label>
    </fieldset>
  
  </form>
  
<em>Note: blank lines added for clarity; they would not appear in the actual output</em>

subform options:

:inputs :: Automatically call +inputs+ with the given values.  Using
           this, it is not required to pass a block to the method,
           though it will still work if you do.
:inputs_opts :: When using the :grid option, this allows you to specify options
                to pass to the table InputsWrapper.
:legend :: Overrides the default :legend used (which is based on the
           association name).  You can also use a proc as the value,
           which will called with each associated object (and the position
           in the associated object already for *_to_many associations),
           and should return the legend string to use for that object.
:grid :: Sets up a table with one row per associated object, and
         one column per field.
:labels :: When using the :grid option, override the labels that would
           be created via the :inputs option.  If you are not providing
           an :inputs option or are using a block with additional inputs,
           you should specify this option.
:skip_primary_key :: Skip adding a hidden primary key field for existing
                     objects.

== Handling form submissions

The Sequel forme plugin only handles creating the forms, it does not handle processing
input submitted via forms.  For a form such as:

  Forme.form(Album[1], :action=>'/foo') do |f|
    f.input :name
    f.input :copies_sold
  end

Input of the form will often be submitted as the following parameter hash (this depends
on your web framework, but Rack works this way by default):

  {'album'=>{'name'=>'Rising Force', 'copies_sold'=>'100000'}}

One way to handle the form submission is to use Sequel::Model#set_fields.

  album = Album[1]
  album.set_fields(params['album'], %w'name copies_sold')
  album.save

Note that you have to specify the parameter names again as the second argument to
set_fields.

Handling submitted parameters becomes more complex as your forms become more complex.
For example, if you are only displaying certain form fields in certain situations:

  album = Album[1]
  Forme.form(album, :action=>'/foo') do |f|
    f.input :name
    f.input :copies_sold if album.released?
  end

Then your parameter handling becomes more complex:

  album = Album[1]
  album.set_fields(params['album'], %w'name')
  album.set_fields(params['album'], %w'copies_sold') if album.released?
  album.save

As you can see, you basically need to recreate the conditionals used when creating
the form, so that that the processing of the form submission handles only the
inputs that were displayed on the form.

=== Sequel forme_set plugin

The Sequel forme_set plugin is designed to make handling form submissions easier.  What it does
is record the form fields that are used on the object, and then it uses those fields
to handle input submitted for the object.  For example:

  album = Album[1]
  Forme.form(album, :action=>'/foo') do |f|
    f.input :name
    f.input :copies_sold if album.released?
  end
  album.forme_set(params['album'])

If the album has been released, and the form would display the name and copies_sold
inputs, then forme_set will accept input for both.  If the album has not been released,
the form will only display the name input, so forme_set will only accept the name input.

So forme_set offers two advantages over using set_fields:

1. DRYs up code as you don't have to specify the names twice
2. Simplifies complex form submissions by eliminating duplication of conditionals

==== Validations

forme_set offers one additional advantage over using set_fields.  When dealing with
associations, set_fields does not check that the value submitted for an input matches
one of the available options displayed on the form.  For example, if you have a form such as:

  Forme.form(album, :action=>'/foo') do |f|
    f.input :name
    f.input :artist, :dataset=>proc{|ds| ds.where{name > 'M'}}
  end

The form will only display artists whose name is greater than 'M'.  However, if you process
input using:

  album.set_fields(params['album'], %w'name artist_id')

Then a malicious user can submit an artist_id for an artist whose name is not greater than 'M',
and the value will be set.  In addition to setting values, forme_set also adds validations
that the submitted values for associated objects match one of the options displayed on the form,
which can increase security.

==== Usage

Because forme_set relies on creating form inputs using the same model instance that will
be used for accepting input, using it often requires some code rearranging.  If you are
storing Forme::Form objects and later using them on forms, it is fairly simple to move the
Forme::Forme object creation to a method, that you can call both in the initial display
and when processing the input:

  def album_form(album)
    Forme.form(album, :action=>'/foo') do |f|
      f.input :name
      f.input :copies_sold
    end
  end

Then when displaying the form:

  <%= album_form(Album[1]) %>

and when processing the form's input:

  album = Album[1]
  album_form(album)
  album.forme_set(params['album'])

However, if you use Forme's ERB/Rails template integration (see below), and are inlining
forms in your templates, unless you want to extract the Forme::Form creation to methods,
you have to basically rerender the template when processing the input.  How you do
this is specific to the web framework you are using, but is it similar to:

  @album = Album[1]
  render :template
  @album.forme_set(params['album'])

==== Caveats

forme_set is not perfect, there are ways to use Forme that forme_set will not handle
correctly.  First, forme_set only works with forms that use model objects, and doesn't
handle inputs where the +:obj+ option is provided to change the input.
Additionally, forme_set does not currently handle subform/nested_attributes.

In cases where forme_set does not handle things correctly, you can use +forme_parse+,
which will return metadata that +forme_set+ uses (+forme_set+ calls +forme_parse+
internally).  +forme_parse+ returns a hash with the following keys:

:values :: A hash of values that can be used to update the model, suitable for passing
           to Sequel::Model#set.
:validations :: A hash of values suitable for merging into forme_validations. Used to
                check that the submitted values for associated objects match one of the
                options for the input in the form.

It is possible to use +forme_set+ for the forms it can handle, and use +forme_parse+ and
+set_fields+ for other forms.

=== Roda forme_set plugin

The Roda forme_set plugin builds on the Sequel forme_set plugin and is designed to make
handling form submissions even easier. This plugin adds a hidden form input to store which
fields were used to build the form, as well as some other metadata.  It adds another hidden
form input with an HMAC, so that on submission, if the HMAC matches, you can be sure that an
attacker didn't add extra fields.

There are a couple advantages to this plugin over using just the Sequel forme_set plugin.
One is that you do not need to record the form fields when processing the submission of a
form, since the information you need is included in the form submission. Another is that
calling the +forme_set+ method is simpler, since it can determine the necessary parameters.

While you need code like this when using just the Sequel forme_set plugin:

  album = Album[1]
  Forme.form(album, :action=>'/foo') do |f|
    f.input :name
    f.input :copies_sold if album.released?
  end
  album.forme_set(params['album'])

when you also use the Roda forme_set plugin, you can simplify it to:

  album = Album[1]
  forme_set(album)

==== Validations

The Roda forme_set plugin supports and uses the same validations as the Sequel forme_set
plugin.  However, the Roda plugin is more accurate because it uses the options that were
present on the form when it was originally built, instead of the options that would be
present on the form when the form was submitted.  However, note that that can be a
negative if you are dynamically adding values to both the database and the form between
when the form was built and when it was submitted.

==== Usage

Because the Roda forme_set plugin includes the metadata needed to process the form in form
submissions, you don't need to rearrange code to use it, or rerender templates.
You can do:

  album = Album[1]
  forme_set(album)

And the method will update the +album+ object using the appropriate form values. 

Note that using the Roda forme_set plugin requires you set a secret for the HMAC.  It
is important that you keep this value secret, because if an attacker has access to this,
they would be able to set arbitrary attributes for model objects.  In your Roda class,
you can load the plugin via:

  plugin :forme_set, :secret => ENV["APP_FORME_HMAC_SECRET"]

By default, invalid form submissions will raise an exception.  If you want to change
that behavior (i.e. to display a nice error page), pass a block when loading the plugin:

  plugin :forme_set do |error_type, obj|
    # ...
  end

The block arguments will be a symbol for the type of error (:missing_data, :missing_hmac,
:hmac_mismatch, :csrf_mismatch, or :missing_namespace) and the object passed to +forme_set+.
This block should raise or halt.  If it does not, the default behavior of raising an
exception will be taken.

=== Form Versions

The Roda forme_set plugin supports form versions.  This allows you to gracefully handle
changes to forms, processing submissions of the form generated before the change (if
possible) as well as the processing submissions of the form generated after the change.

For example, maybe you have an existing form with just an input for the name:

  form(album) do |f|
    f.input(:name)
  end

Then later, you want to add an input for the number of copies sold:

  form(album) do |f|
    f.input(:name)
    f.input(:copies_sold)
  end

Using the Roda forme_set plugin, submissions of the old form would only set the
name field, it wouldn't set the copies_sold field, since when the form was created,
only the name field was used.

You can handle this case be versioning the form when making changes to it:

  form(album, {}, :form_version=>1) do |f|
    f.input(:name)
    f.input(:copies_sold)
  end

When you are processing the form submission with forme_set, you pass a block, which
will be yielded the version for the form (nil if no version was set):

  forme_set(album) do |version|
    if version == nil
      album.copies_sold = 0
    end
  end

The block is also yielded the object passed for forme_set, useful if you don't keep
a reference to it:

  album = forme_set(Album.new) do |version, obj|
    if version == nil
      obj.copies_sold = 0
    end
  end

You only need to support old versions of the form for as long as their could be
active sessions that could use the old versions of the form.  As long you as
are expiring sessions to prevent session fixation, you can remove the version
handling after the expiration period has passed since the change to the form
was made.

Note that this issue with handling changes to forms is not specific to the Roda
forme_set plugin, it affects pretty much all form submissions.  The Roda forme_set
plugin just makes this issue easier to handle.

==== Caveats

The Roda forme_set plugin has basically the same caveats as Sequel forme_set plugin.
Additionally, it has a couple other restrictions that the Sequel forme_set plugin
does not have.

First, the Roda forme_set plugin only handles a single object in forms,
which must be provided when creating the form.  It does not handle multiple
objects in the same form, and ignores any fields set for an object different
from the one passed when creating the form.  You can use the Sequel forme_set
plugin to handle form submissions involving multiple objects, or for the
objects that were not passed when creating the form.

Second, the Roda forme_set plugin does not handle cases where the field values
are placed outside the form's default namespace.  The Sequel forme_set plugin
can handle those issues, as long as all values are in the same namespace, since
the Sequel forme_set plugin requires you pass in the specific hash to use (the
Roda forme_set plugin uses the form's namespace information and the submitted
parameters to determine the hash to use).

In cases where the Roda forme_set does not handle things correctly, you can use
forme_parse, which will return metadata in the same format as the Sequel plugin
forme_parse method, with the addition of a +:form_version+ key in the hash for the
form version.

It is possible to use the Roda forme_set plugin for the submissions it can handle, the
Sequel forme_set plugin for the submissions it can handle, and set other fields manually
using the Sequel +set_fields+ methods.

Note that when using the Roda forme_set plugin with an existing form, you should first
enable the Roda plugin without actually using the Roda forme_set method.  Do not
start using the Roda forme_set method until all currently valid sessions were
established after the Roda forme_set plugin was enabled.  Otherwise, sessions that
access the form before the Roda forme_set plugin was enabled will not work if they
submit the form after the Roda forme_set plugin is enabled.

== Other Sequel Plugins

In addition to the Sequel plugins mentioned above, Forme also ships with additional Sequel
plugins:

forme_i18n :: Handles translations for labels using i18n.

= Roda Support

Forme ships with multiple Roda plugins

* forme_set (discussed above)
* forme
* forme_route_csrf
* forme_erubi_capture_block
* forme_erubi_capture

== forme_route_csrf and forme plugins

For new code, it is recommended to use forme_route_csrf, as that uses Roda's route_csrf
plugin, which supports more secure request-specific CSRF tokens.  In both cases, usage in ERB
templates is the same:

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

The forme_route_csrf plugin's +form+ method supports the following options
in addition to the default +Forme.form+ options:

:emit :: Set to false to not emit implicit tags into template.  This should only be
         used if you are not modifying the template inside the block.
:csrf :: Set to force whether a CSRF tag should be included.  By default, a CSRF
         tag is included if the form's method is one of the request methods checked
         by the Roda route_csrf plugin.
:use_request_specific_token :: Set whether to force the use of a request specific
                               CSRF token.  By default, uses a request specific
                               CSRF token unless the Roda route_csrf plugin has been
                               configured to support non-request specific tokens.

The <tt>emit: false</tt> option allows you to do:

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

This is useful if you are calling some method that calls +form+ with a block,
where the resulting entire Forme::Forme object will be literalized into the
template. The form will include the CSRF token and forme_set metadata as appropriate.

The forme plugin does not require any csrf plugin, but will transparently use
Rack::Csrf if it is available. If Rack::Csrf is available a CSRF tag if the form's
method is +POST+, with no configuration ability.

== forme_erubi_capture_block plugin

The forme_erubi_capture_block plugin builds on the forme_route_csrf plugin, but it supports
the erubi/capture_block engine, which allows this syntax:

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

If you use the forme_erubi_capture)block plugin, you need to manually set Roda to use the
erubi/capture_block engine, which you can do via:

  require 'erubi/capture_block'
  app.plugin :render, :engine_opts=>{'erb'=>{:engine_class=>Erubi::CaptureBlockEngine}}

The forme_erubi_capture plugin requires Erubi 1.13.0+.

== forme_erubi_capture plugin

The forme_erubi_capture plugin builds on the forme_route_csrf plugin, but it supports
the erubi/capture_end engine, which allows this syntax:

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

If you use the forme_erubi_capture plugin, you need to manually set Roda to use the
erubi/capture_end engine, which you can do via:

  require 'erubi/capture_end'
  app.plugin :render, :engine_opts=>{'erb'=>{:engine_class=>Erubi::CaptureEndEngine}}

The forme_erubi_capture plugin requires Roda 3.50.0+.

= Sinatra Support

Forme ships with a Sinatra extension that you can get by <tt>require "forme/erb"</tt> and using
<tt>including Forme::ERB::Helper</tt>.  This is tested to support ERB templates in Sinatra.
It allows you to use the following API in your erb templates:

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

In order to this to work transparently, the ERB outvar needs to be <tt>@_out_buf</tt> (this is the
default in Sinatra).  The Sinatra extension also supports the <tt>emit: false</tt> option to not
directly modify the related template (see example in the Roda section for usage).

= Rails Support

Forme ships with a Rails extension that you can get by <tt>require "forme/rails"</tt> and using
<tt>helper Forme::Rails::ERB</tt> 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-7.0.

= Input Types and Options

These are the types and options supported by Forme::Input objects, usually
created via Forme::Form#input:

== General Options

These options are supported by all of the input types:

:attr :: The attributes hash to use for the given tag, attributes in this hash
         take precedence over other options that set attributes.
:autofocus :: Set the autofocus attribute if true
:class :: A class to use.  Unlike other options, this is combined with the
          classes set in the :attr hash.
:dasherize_data :: Automatically replace underscores with hyphens for symbol
                   data attribute names in the +:data+ hash. Defaults to
                   +false+.
:data :: A hash of data-* attributes for the resulting tag.  Keys in this hash
         will have attributes created with data- prepended to the attribute name.
:disabled :: Set the disabled attribute if true
:error :: Set an error message, invoking the error_handler
:error_handler :: Set a custom error_handler, overriding the form's default
:help :: Set help text to use, invoking the helper
:helper :: Set a custom helper, overriding the form's default
:id :: The id attribute to use
:key :: The base to use for the name and id attributes, based on the current
        namespace for the form.
:label :: Set a label, invoking the labeler
:labeler :: Set a custom labeler, overriding the form's default
:name :: The name attribute to use
:obj :: Set the form object, overriding the form's default
:placeholder :: The placeholder attribute to use
:required :: Set the required attribute if true
:type :: Override the type of the input when the form has an associated object
         but the object does not respond to +forme_input+
:value :: The value attribute to use for input tags, the content of the textarea
          for textarea tags, or the selected option(s) for select tags.
:wrapper :: Set a custom wrapper, overriding the form's default

== Input Type-Specific Options

=== :checkbox

Creates an input tag with type checkbox, as well as a hidden input tag. Options:

:checked :: Mark the checkbox as checked.
:hidden_value :: The value to use for the hidden input tag.
:no_hidden :: Don't create a hidden input tag.

=== :radio

Creates an input tag with type radio. Options:

:checked :: Mark the radio button as checked.

=== :date / :datetime

By default, creates an input tag with type date or datetime.  With the <tt>as: :select</tt> option,
creates multiple select options. Options:

:as :: When value is :select, uses 3 or 6 select boxes by default.
:order :: The order of select boxes when using <tt>as: :select</tt>.  Entries should be a symbol
          for the select field and string to use a string
          (:date default: <tt>[:year, '-', :month, '-', :day]</tt>)
          (:datetime default: <tt>[:year, '-', :month, '-', :day, ' ', :hour, ':', :minute, ':', :second]</tt>)
:select_labels :: The labels to use for the select boxes.  Should be a hash keyed by the
                  symbol used (e.g. <tt>{:month=>'Month'}</tt>).  By default, no labels are used.
:select_options :: The options to use for the select boxes.  Should be a hash keyed by the
                   symbol used in order (e.g. <tt>{:year=>1970..2020}</tt>).  The values
                   can be a number used as both the value and the text of the option or
                   an array with two elements, the first of which is the value for the option
                   and the second of which is the text for the option.

=== :select

Creates a select tag, containing option tags specified by the :options option.  Options:

:add_blank :: Add a blank option if true.  If the value is a string,
              use it as the text content of the blank option.  The default value can be
              set with Forme.default_add_blank_prompt, and defaults to the empty string.
:blank_attr :: If :add_blank is set, sets the attributes to use for the blank option.
:blank_position :: If :add_blank is set, can be set to :after to add the prompt after
                   the inputs, instead of before (which is the default).
:multiple :: Creates a multiple select box.
:optgroups :: An enumerable of pairs with the first element being option group labels or
              a hash of option group attributes, and values being enumerables of options
              (as described by :options below).  Creates optgroup tags around the
              appropriate options. This overrides any options specified via :options.
:options :: An enumerable of options used for creating option tags.
            If the :text_method and :value_method are not given and the entry is an 
            array, uses the first entry of the array as the text of the option, and
            the last entry of the array as the value of the option. If the last entry
            of the array is a hash, uses the hash as the attributes for the option.
            If the option value is +:hr+, uses an hr tag (allowed in recent versions of
            the HTML standard).
:selected :: The value that should be selected.  Any options that are equal to
             this value (or included in this value if a multiple select box),
             are set to selected.
:size :: Uses the size attribute on the tag
:text_method :: If set, each entry in the array has this option called on
                it to get the text of the object.
:value :: Same as :selected, but has lower priority.
:value_method :: If set (and :text_method is set), each entry in the array
                 has this method called on it to get the value of the option.

=== :checkboxset

Creates a set of checkbox inputs all using the same name.  Supports the same options
as the :select type, except that the :multiple option is assumed to be true.  Also supports
the following options:

:tag_wrapper :: The wrapper transformer for individual tags in the set
:tag_labeler :: The labeler transformer for individual tags in the set
:tag_label_attr :: The attributes to use for labels for individual tags in the set

=== :radioset

Creates a set of radio buttons all using the same name.  Supports the same options
as the :checkboxset type.

=== :textarea

Creates a textarea tag.  Options:

:cols :: The number of columns in the text area.
:rows :: The number of rows in the text area.
:maxlength :: Use the maxlength attribute on the tag
:minlength :: Use the minlength attribute on the tag

== all others

Creates an input tag with the given type. This makes it easy to use inputs such
as text and password, as well as newer HTML5 inputs such as number or email. Options:

:size :: Uses the size attribute on the tag
:maxlength :: Use the maxlength attribute on the tag
:minlength :: Use the minlength attribute on the tag

== Form options

These are the options supported by Forme::Form object, mostly used to set
the defaults for Inputs created via the form:

:after :: A callable object that is yielded the Form instance after yielding to
          the block. Can be used to add hidden inputs to the end of the form.
:before :: A callable object that is yielded the Form instance before yielding to
          the block. Can be used to add hidden inputs to the start of the form.
:config :: The configuration to use, which automatically sets defaults
           for the transformers to use.
:errors :: A Hash of errors from a previous form submission, used to set
           default errors for inputs when the inputs use the :key option.
:error_handler :: Sets the default error_handler for the form's inputs
:helper :: Sets the default helper for the form's inputs
:formatter :: Sets the default formatter for the form's inputs
:input_defaults :: Sets the default options for each input type.  This should
                   be a hash with input type keys, where the values are the
                   hash of default options to use for the input type. 
:inputs_wrapper :: Sets the default inputs_wrapper for the form
:labeler :: Sets the default labeler for the form's inputs
:namespace :: Sets the default namespace(s) to use for the form.  Namespacing
              will automatically create namespaced name and id attributes for
              inputs that use the :key option.
:obj :: Sets the default +obj+ for the form's inputs.
:serializer :: Sets the serializer for the form
:values :: The values from a previous form submission, used to set default
           values for inputs when the inputs use the :key option.
:wrapper :: Sets the default wrapper for the form's inputs

For forms created by Forme.form, the following options are supported:

:inputs :: An array of inputs to create inside the form, before yielding
           to the block.
:button :: A button to add to the form, after yielding to the block.

= Internal Architecture

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:

<tt>Forme::Form</tt> :: main object
<tt>Forme::Input</tt> :: high level abstract tag (a single +Input+ could represent a select box with a bunch of options)
<tt>Forme::Tag</tt> :: low level abstract tag representing an HTML tag (there would be a separate +Tag+ for each option in a select box)

The difference between <tt>Forme::Input</tt> and <tt>Forme::Tag</tt> 
is that <tt>Forme::Tag</tt> directly represents the underlying HTML
tag, containing a type, optional attributes, and children, while the
<tt>Forme::Input</tt> is more abstract and attempts to be user friendly.
For example, these both compile by default to the same select tag:

  f.input(:select, :options=>[['foo', 1]])
  # or
  f.tag(:select, {}, [f.tag(:option, {:value=>1}, ['foo'])])

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.
The processing of high level <tt>Forme::Input</tt>s into raw HTML
fragments is performed through the following transformers:

+Formatter+ :: converts a <tt>Forme::Input</tt> instance into a
               <tt>Forme::Tag</tt> instance (or array of them).
+ErrorHandler+ :: If the <tt>Forme::Input</tt> instance has a error,
                  takes the formatted tag and marks it as having the error.
+Helper+ :: If the <tt>Forme::Input</tt> instance has any help text,
            adds the help text in a separate tag.
+Labeler+ :: If the <tt>Forme::Input</tt> instance has a label,
             takes the formatted output and labels it.
+Wrapper+ :: Takes the output of the formatter, labeler, and
             error_handler transformers, and wraps it in another tag (or just
             returns it unmodified).
+Serializer+ :: converts a <tt>Forme::Tag</tt> instance into an
                HTML string.

Technically, only the +Serializer+ is necessary.  The Forme::Form#input
and Forme::Form#tag methods internally create +Input+ and
+Tag+ objects.  Before returning results, the input or tag is converted
to a string using +to_s+, which calls the appropriate +Serializer+.
The +Serializer+ calls the appropriate +Formatter+ if it encounters an
+Input+ instance, and attempts to serialize the output of that (which is
usually a +Tag+ instance).  It is up to the +Formatter+ to call the +Labeler+,
+ErrorHandler+, +Helper+, and/or +Wrapper+.

The Forme::Form object takes the transformers as options (:formatter,
:labeler, :error_handler, :helper, :wrapper, and :serializer), all of which
should be objects responding to +call+ (so you can use <tt>Proc</tt>s) or be symbols
registered with the library using <tt>Forme.register_transformer</tt>:

  Forme.register_transformer(:wrapper, :p) do |tag, input|
    input.tag(:p, {}, tag)
  end

Most transformers are called with two arguments, +tag+ and +input+.  +tag+
is a <tt>Forme::Tag</tt> instance, and +input+ is a <tt>Forme::Input</tt>
instance.  The +Formatter+ and +Serializer+ transformers are the two
exceptions, with +Formatter+ being called with just an +input+, and
+Serializer+ potentionally being called with any object.  The +Serializer+
will in general recursively call itself with children of the argument
given until a string is returned.

There is also an +InputsWrapper+ transformer, that is called by
Forme::Form#inputs.  It's used to wrap up a group of
related options (in a +fieldset+ by default).  It takes +form+
(Forme::Form instance) and +input_opts+ (+Hash+) arguments.

Most of the transformers can be overridden on a per instance basis by
passing the appropriate option to +input+ or +inputs+:

  f.input(:name, :wrapper=>:p)

Existing transformers can be easily extended (ie, to set the class attribute),
by creating your own transformer and then calling the existing transformer.

  Forme.register_transformer(:labeler, :explicit) do |tag, input|
    input.opts[:label_attr] ||= { :class => 'label' }
    Forme::Labeler::Explicit.new.call(tag, input)
  end

== Transformer Types

You can override the type of transform for each form or input using the
following options:

+serializer+ :: tags input/tag, returns string
+formatter+ :: takes input, returns tag
+error_handler+ :: takes tag and input, returns version of tag with errors noted
+helper+ :: takes tag and input, returns version of tag with help added
+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 +labeler+  if the <tt>:label</tt>
option is set, the +error_handler+ if the <tt>:error</tt> option is set, and the +helper+ if the <tt>:help</tt> option
is set .  Finally, it calls the +wrapper+ to wrap the resulting tag before returning it.

The +inputs_wrapper+ is called by <tt>Forme::Form#inputs</tt> 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+

:after_legend :: designed for usage with :legend labeler, putting error message after legend, adding error for first input in the set
:default :: modifies tag to add an error class and adds a span with the error message
:set :: default error_handler for checkboxset and radioset inputs, that adds an error to the last input in the set

This supports the following options:

:error_attr :: A hash of attributes to use for the span with the error message

=== +helper+

:default :: adds a span with the help text

This supports the following options:

:helper_attr :: A hash of attributes to use for the span with the help 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
:legend :: adds a legend before the tags, mostly useful for accessible checkboxset and radioset inputs
:span :: default labeler for checkboxset and radioset inputs that adds a span before the tags

The :default and :explicit labelers respect the following options:

:label_position :: Can be set to :before or :after to place the label before or after the the input.
:label_attr :: A hash of attributes to use for the label tag

=== +wrapper+

:default :: returns tag without wrapping
:div :: wraps tag in div tag
:fieldset :: wraps tags in a fieldset, mostly useful for accessible checkboxset and radioset inputs
:fieldset_ol :: same as :li, but also sets +inputs_wrapper+ to :fieldset_ol
:li :: wraps tag in li tag
:ol :: same as :li, but also sets +inputs_wrapper+ to :ol
:p :: wraps tag in p tag
:span :: wraps tag in span tag
:table :: same as :trtd, but also sets +inputs_wrapper+ to :table
:td :: wraps tag in a td tag
:tr :: same as :td, but also sets +inputs_wrapper+ to :tr
: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

All of these except for :default respect the following options:

:wrapper_attr :: A hash of attributes to use for the wrapping tag.

=== +inputs_wrapper+

:default :: uses a fieldset to wrap inputs
:div :: uses a div tag to wrap inputs
:fieldset_ol :: use both a fieldset and an ol tag to wrap inputs
:ol :: uses an ol tag to wrap inputs, useful with :li wrapper
:table :: uses a table tag to wrap inputs, useful with :trtd wrapper
:tr :: uses a tr tag to wrap inputs, useful with :td wrapper

All of these support the following options:

:attr :: A hash of attributes to use for the wrapping tag.

The :default, :fieldset_ol, and :table inputs_wrappers support the following options:

:legend :: A text description for the inputs, using the legend tag for fieldsets and
           the caption tag for a table.
:legend_attr :: A hash of attributes for the legend/caption tag.

The :table inputs_wrapper also supports the following options:

:labels :: An array of labels, used to setup a row of table headers with the labels.

== 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

=== Bootstrap Support

Forme ships with support for Bootstrap 5 HTML formatting. This support is shipped in
it's own file, so if you don't use it, you don't pay the memory penalty for loading
it.

  require 'forme/bs5'
  Forme.default_config = :bs5

There is also support for Bootstrap versions 3-4:

  require 'forme/bs3'
  Forme.default_config = :bs3

= 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.

= License

MIT

= Author

Jeremy Evans <code@jeremyevans.net>
