Processing form input is a common task for web applications and is very tedious when you must write code to check the validity of each input field, display errors and the field inputs if a field has invalid data, and convert the field to the proper type of value such as a number, date, etc. Fortunately Cerise comes with code to do all of these tasks and much more!

This document assumes an understanding of Cerise templates, so please look there for details about the templates. To see a working demo, please check out the form.rb and form.ahtml files in your cerise/apps/examples/ directory.

Cerise Forms

There are only a few differences between a form managed by Cerise and a standard HTML form. First, the Cerise form is an Cerise template with HTML tags containing the errors to display. This makes for easy modification and translation since the HTML page itself contains the error text. It also allows the page designer to see how the error will be presented. Second, the form input tags have template attribute accessors to display the user input when a form is redisplayed with error messages.

Error Tags

Form input errors may be displayed in any fashion desired. The error ids corresponding to each form input error are passed to the template as key value pairs in the form { :error1_id => true, :error2_id => true, ... }, so if a given error id is present, the HTML tag with a matching id attribute is displayed. A common way to display errors is in a list at the top of the form such as:

Accomplishing this with Cerise is easy!

    <ul class="errors">
      <li id="err001">field 1 must be 6-9 characters long</li>
      <li id="field2_missing">field 2 is a required field</li>
    </ul>
  

If neither of these errors occurs, the list will not be displayed. Of course using a list is not required, you can put any tag containing the error anywhere in the page, perhaps next to the field with the error.

Redisplaying User Input

When form input does have errors, the form should be redisplayed with the appropriate error messages as well as the users input. It would be very annoying to have to refill a form just because one field has an error. Continuing with the same example from above, here is what the form would look like before the user submits:

When this form is submitted, errors are displayed since the value in 'field 1' is not long enough, and there is no value for 'field 2'. The form should look like this now:

This is also very easy with Cerise! The values passed in the form submittal are returned to the template in several forms, suitable for the different <input/> tags. This is necessary since the <select/> and <input type="radio"/> tags have the rather ugly "selected" attribute if they are selected.

The previous form example would have the following HTML code, which simply substitutes the form's field value for the "@name". This style works for text, password (not recommended, storing the password in the HTML page is bad mojo!), file, and textarea input types. It also works for the checkbox type if the Cerise::FormHandler declares it as a boolean type, otherwise the other method should be used.

    <input type="text" name="field1" value="@field1"/>
    <input type="text" name="field2" value="@field2"/>
  

For the other input types, a more roundabout way is required. This uses the template feature of deleting an tag attribute if there is no value supplied for it. If the user selects 'option 1' of a three option <select/> element, then that option will be passed to the template as { :__option_value => true } and the attribute will be preserved.

    <select name="country">
      <option value="japan" selected="@__japan">Japan</option>
      <option value="usa" selected="@__usa">USA</option>
    </select>
    
    <label>English:</label>
    <input type="radio" name="radio" checked="@__english" value="english"/>
    
    <label>Japanese:</label>
    <input type="radio" name="radio" checked="@__japanese" value="japanese"/>
  

FormHandler

Behind the scenes of all the form magic lies Cerise::FormHandler. To take advantage of this functionality, the request handler corresponding to the form template template must extend it. When the TemplateHandler calls a subclass of FormHandler a specific sequence of methods are called to load, validate, and submit the form.

  1. load(request, response) when the form is first requested, or validate() fails
  2. validate(form) when the form is submitted
  3. submit(request, response) when validate() succeeds

load(request, response)

The load() method should return the template data model for any dynamic content on the form. It is called when the form is loaded via a HTTP GET, and when it is redisplayed with errors. Care must be taken to ensure that none of the data model keys that load() returns will conflict with keys from validate(), i.e. field names and error ids, as the two data models are merged. The default implementation returns an empty hash: { }.

validate(form)

The core of the form validation code is in the validate() method which should not be overridden unless the handler will do custom form field validation, or at least call super.validate(). The form parameter is a hash of the form fields that were populated with a value: { "field" => "value" ... }

validate() calls another method of the handler called form_fields which should return a hash of all the form fields needing to be validated. This hash must have entries in the following format:

    "field_name" => {
      :type => :Type,
      :optional => false,      
      :rules => [['rule1', :err_id_1], ['rule2', : :err_id_2]]
    }
  
keydescription
"field_name" the name of the field in the <input/> tag
:type the type of the field, one of :String, :Fixnum, :Float, :Boolean, or :Date
:optional is not required, but when present indicates if the field is optional or must be submitted
:rules a list of rule and error id pairs. The rule is evaluated against the submitted value, and if the resulting is false then the error id is returned so the appropriate error message can be displayed in the template.

The rule may be one of the following types:

typedescription
Proc called with the input value as the only parameter and must return true or false
String passed to eval() in the form eval("value #{rule}")
Symbol the name of an instance method which is called with the input value as the only parameter and must return true or false

When validate() detects any errors in the form inputs, it returns an template data model consisting of the form input the error ids, and anything from load(). As previously mentioned the model looks like this:

If a field has no errors then it is assigned to an instance variable of the same name using the attribute accessor field=.

To achieve results as in the original example, the FormHandler subclass for this demonstration form would return the following form_fields:

    "field1" => {
      :type => :String,
      :optional => false,      
      :rules => [['=~ /\w{6,9}/', :err001]]
    }

    "field2_name" => {
      :type => :String,
      :optional => false,      
      :rules => [ ]
    }
  

Since the data entered into 'field1' is "foo" which is only 3 characters long, the rule will fail and error ':err001' will be displayed. Since no data is entered into 'field2' and it is not an optional field, the special ':<field>_missing' error will be displayed. Note that this error id is not placed in the rules, since no rules are applied to a nonexistent field. Instead if a required field is missing, the error id :<field>_missing will be returned. The template data model returned from this example's validate() looks thus:

    { :field1 => "foo", :field2 => "", :err001 => true, 
      :field2_missing => true, :__foo => true }
  

The line :__foo => true looks a bit odd. This is the trick mentioned in the <select/> redisplay section.

submit(request, response)

The submit() method is called after a successful call to validate(). At this point all of the form inputs have been validated, converted into the appropriate type, and assigned to instance variables of the request handler. The appropriate action should be taken to finish processing of the form. The default implementation returns an empty hash { } which causes the form to be redisplayed, but a real application would typically return a String indicating the next page to display, which the user would then be redirected to.