Developers > Kernel Documentation > HTML formatting > FormBuilder.pm

FormBuilder.pm


FormBuilder

This is a simple utility for building forms. It is content-agnostic, so it can easily be adapted to various web applications. It is used by higher-level form construction methods in ExSite, which handle all of the ExSite-specific logic.

Usage

A typical usage is shown below.

  1. Make a FormBuilder object
  2.     my $f = new ExSite::FormBuilder(method=>"post",action=>"/cgi-bin/foo.cgi");

    In addition to method and action, you can also specify enctype, which is usually set to ``multipart/form-data'' for forms that allow file uploads, and name, which may be used by Javascript to access the form.

  3. Alter the basic form behaviours
  4. These can also be specified in the new() parameters:
        $f->method($get_or_post);
    $f->action($url);
    $f->enctype($encoding_type);
    $f->name($name);

  5. Specify a form template
  6. This is optional, as a default template to lay out the form will be provided. See the Templating section below for details.
        $f->template($all_in_one_template);
    $f->template($top_of_form, $row_by_row_template, $bottom_of_form);

  7. Add input fields to the form
  8.     $f->input(%first_input);
    $f->input(%second_input);
    $f->input(%etc);

    The allowed options are described below, under Input Parameters. Note that you can also use the following methods for textarea and select inputs:

        $f->textarea(%options);
    $f->select(%options);

    These work similarly to input(), except that you do not need to specify an input type.

  9. Generate the form
  10.     print $f->make();
  11. Reset the form object, in case you want to re-use it.
  12.     $f->init();

In practice, these steps can be done in any order. Templates will override any previously-specified templates, but are not actually used until make() is called. make() will only output a form that reflects the state of the object up to that point.

More advanced usage may involve installation of custom validation scripts, and more sophisticated use of the input generation functions. Details are given below.

Input Parameters

Input parameters are passed to the input() method in an %options hash.

Input tag attributes

name
The input field name.

type
The input type, one of text (default), password, file, checkbox, radio, hidden, textarea, and select. You can also use a type of ``preformatted'' if you are passing in a preformatted string of HTML to insert into the form.

value
The preset value of the field.

id
The ID of the input element. This is optional, but if you do not provide one, FormBuilder may set one automatically if needed for validation.

class
The CSS class of the input element.

accept
The accept attribute, used by some file uploads.

maxlength
Sets the maximum length of a string that will be accepted in text fields.

size
Sets the width of the input field for text fields.

Input field flags

The following parameters accept true/false values:

checked
Preselects the given input, for radio and checkbox inputs.

readonly
Input field is enabled and will be submitted, but cannot be modified.

disabled
Form element is greyed out, and will not be submitted.

required
Form element is required, and form will not submit unless a value is specified. (See Validation, below.) This is treated as a true/false value, but there are two true values you can pass.

(1) A basic true value of 1 means the field is required, and automatic Javascript validation will be performed.

(-1) A value of -1 means the field is required, but validation will only be performed server-side, not in Javascript. The field will be styled using CSS as a required field, however.

Other modifiers

The following parameters are also accepted, although they are not input attributes:

input
You can specify the complete, literal input element with this parameter, if you wish to override the automatically-constructed one.

nullvalue
For select inputs, this adds a null option ('') as the first option in the list. The value of this setting is the string displayed in the options menu. A typical value is ``== select ==''.

prompt
Specify the human-readable prompt string that informs the viewer what input is expected in this field. If no prompt is provided, the element name will be used as a prompt.

top
If true, the input will be added at the top of the form, instead of at the end. (This only works with row-by-row templates.)

tail
This is an optional string of HTML that will be appended to the input field. It can be used to include help text or hints.

Note that each input is generated as a new form field, unless it is a radio input of the same name as a previous input; in that case, the radio button is appended to the other radio buttons of that name.

Form Presets

You can pass individual field values in to each call to input, but you can also initialize all input values with a single call to values:

    $f->values(%data);

%data is a hash of input names => form values. One convenient application of this is regenerating a form that was submitted with invalid inputs.


Validation

FormBuilder allows for client-side validation of forms.

If you specify the ``required'' parameter for any inputs in your form, then special validation javascript will be built in to ensure that these fields contain data before the form will submit. We only check that data is present; we do not check that it conforms to any particular pattern. To do pattern checks, use a custom validation routine (see below).

Validation is automatic; you need not do anything other than include the ``required'' parameter in your input() %options where needed.

This is a convenience for the end-user, to prevent unnecessary submissions and annoying waits in case of an incorrectly-filled form. It can be circumvented, so it does not replace server-side validation, and should never be considered a security measure.

Validation requires that the required form elements and the form itself have unique IDs. FormBuilder will assign unique IDs to these elements if you have not specified any yourself. If you specify your own IDs, FormBuilder will try to use those instead. However, it does not validate that the ID you have specified is unique in the form or the document, so take care when specifying your own IDs.

Failed validation will result in a Javascript alert indicating which fields are missing.

To require a user to accept some terms before the form will submit, simply add a checkbox input that is required, eg.

    $f->input(name=>"termsOfService",
prompt=>"I agree to the terms of service",
type=>"checkbox",
required=>1,
);

(Remember that this is not a bulletproof method for ensuring the form does not get submitted without the box checked.)

Warning for radio buttons: radio buttons consist of multiple input tags that are grouped under a single name. If you set a single radio button as required, that means you must select that one button, and any other button choices will not be accepted. This is probably not what you want. Similarly if you make all but one required, then you will not be able to select that one button. To get the behaviour that at least one button in the whole group must be selected, and any button in the group may be selected, then each button in the group must be set as required.

Custom Validation

If you want to inject your own validation javascript into the form, do this:

    $f->validate($my_js_call,$my_js_script);

where $my_js_call is the call to your custom javascript function (eg. ``validate_password(this)''), and $my_js_script contains the full javascript code for this function. It's a good idea to include the (this) in your call, which will pass the form object into your validation function. If you leave off the script parameter, FormBuilder will assume it has already been loaded or inserted into the page.

Your validation script must return true for the form to get submitted. If you return false, you should also throw up your error messages using alert() calls.

You can define any number of custom validation scripts, and they will all be executed, joined by the '&&' operator. Each script can be treated as one validation pass; the last pass does generic automatic validation (testing that form fields contain data).

All passes must return true for the form to be submitted, and if any return false, the remaining passes will not execute. The auto-generated validation script (described above) is run last in this sequence. This allows custom scripts to manipulate form data before final submission.

Example

Say we want to not only test a field to make sure that data has been provided, but also test that it matches an allowed pattern of values. We can set the field as required, to make sure that data is present:

    $f->input(name=>"email",required=>1);

Then we can add a custom validator:

    $f->validate("validate_email(this)",
      "function validate_email(f) {
      var re = new RegExp('.+@.+\\.com');  // not a very good regexp
      if (f.email.value.match(re)) {
        return true;
      }
      alert('Invalid email address!');
      return false;
      }");

The first pass of validation checks that the email field (among others) contains data. If all required fields contain data, then validate_email() is called, which tests the value in the email field against a regular expression. If it passes, it returns true, which will take us to the next validation pass. If not, it throws up an appropriate alert, and returns false, which causes the validation pass to abort, and the form will not submit.

Styling Required Fields

Required files will be wrapped in some CSS markup to allow you to highlight them in some way.

If you want the required field highlighting, but do not want client-side validation (ie. you will handle it server-side), then pass a negative non-zero value (eg.-1) for the required flag on that input. All this does is use the HTML markup for required fields, but does not include any validation javascript.

You can also optionally insert additional text/html before or after the prompt on required fields. Use the object attributes required_suffix or required_prefix, eg.

    $f->set("required_suffix","*");  # append a star to required fields


Custom Inputs

You can manually specify the input field tag using the input option. If your input is simple (single input, unique name), you can use the built-in validation by setting the required option to a non-zero value.

If your custom input is complex (for example, multiple input fields, javascript-driven input controls, etc.), then the built-in validation may not work. Built-in validation checks for non-null value in the form element (ie. f.inputname.value), or if the element is a checkbox, that it has been checked (f.inputname.checked). A compound input (eg. a date field that prompts for year, month, and day) may need to test multiple inputs to completely validate, and the built-in-validation will not do this. In these cases, you will need to leave the required flag off, and supply a custom validation routine. (See above.)

Example (date selector)

Say we want a date selector with separate selectors for day, month, and year. We can call the internal inputtag() (or select() and textarea()) methods to build the actual input fields without altering the form state. We can do this for each of day, month, and year, and concatenate the results together:

    my $date_selector = 
$f->inputtag(type=>"select",name=>"day",options=>[1..31]) .
$f->inputtag(type=>"select",name=>"month",options=>[1..12]) .
$f->inputtag(type=>"select",name=>"year",options=>[1990..2020]);

$date_selector now contains the HTML for selecting a date using this aggregate input. Now we want to add this to our form as a ``single'' input field:

    $f->input(prompt=>"date",input=>$date_selector);

If this field is required, we cannot simply set required=>1 because basic Javascript validation will get confused by all of our sub-fields, so it has no way to easily tell if the field is set or not. In this case, we have three options:

  • server validation
  • set required=>-1 to mark the field as required, but let the validation happen on the server

  • custom validation
  • install our own custom validation script that knows how to inspect our special sub-fields and report problems. See below for details.

  • reformat the data
  • install a custom validation script that aggregates our data into a single value, copies it into a hidden field in the form. That hidden field can be set with required=>1.


Form Templates

Templates allow you some control over how forms are laid out in HTML. There are two styles of template: row-by-row (default), and all-in-one.

Default Template

By default, forms are built using row-by-row templates, ie. by concatenating form fields that are each templated using following HTML:

    

YOUR PROMPT STRING
YOUR INPUT TAG

There are enough CSS hooks in this default template to permit a range of styling to be applied to the forms.

Custom Templates

You can replace the default row-by-row template with your own using this call:

    $f->template($top_of_form, $row_by_row_template, $bottom_of_form);

$top_of_form contains some HTML to place in front of all fields (eg. opening a table). $bottom_of_form contains some HTML to place after all fields (eg. closing a table).

$row_by_row_template contains the the actual template to re-use for each field in the form. It should contain two substitution strings, [[prompt]] and [[input]], which will be replaced with the prompt string and the input tag respectively.

Example

    $f->template("",
"",
"
[[prompt]][[input]]
");

All-in-one Templates

For more precise control over layout, you can provide an all-in-one template that specifies where each named input field should go.

    $f->template($all_in_one_template);

$all_in_one_template is a block of HTML that includes miscellaneous [[name:prompt]] and [[name:input]] tags, where ``name'' is the name of the input field in that position. [[name:prompt]] is replaced by the prompt string for that input field, and [[name::input]] is replaced by the input tag(s) for that field.

Your all-in-one template should include any required CSS classes in the template HTML, as no further HTML insertions are performed.

If you add inputs to your form that are not referenced in the all-in-one template, then FormBuilder will append them to the end of the form using the row-by-row templating method. You can actually specify templates for both methods in this case, by specifying your row-by-row template first, and your all-in-one template second. The last method takes precedence, but the prior method is still saved in case you have untemplated inputs in your form.

Example

    # row-by-row fallback template for unexpected inputs
$f->template("","

[[prompt]]
[[input]]

","");
    # all-in-one template for expected inputs
$f->template("
[[login:prompt]][[login:input]]
[[passwd:prompt]][[passwd:input]]
");

Preformatted Rows

Sometimes you need to break with the row--by-row template, and add some custom HTML (whether an input field or not) onto your form. You can append the special HTML, using an input type of ``preformatted''. FormBuilder will treat the prompt and input values of the input like HTML strings that are simply concatenated and appended to the form.

You can use this trick to insert arbitary HTML text into the middle of your forms, such as help text or section headings:

    $f->input(type=>"preformatted",
prompt=>"

New Section

",
input=>"

This is some helful text introducing this new
section of the form.

");

This example inserts a heading and paragraph into the form, exactly as given. If you provide both a prompt and input, both will be inserted verbatim into the form. If you only include one or the other, only that one will be included.

Try to ensure that your concatenated prompt and input fields will fit into the form HTML. For example, if your form template is a table, and your row-by-row template is a row in this table, then your preformatted input should also be a table row that fits into this HTML. For example:

    $f->input(type=>"preformatted",
prompt=>"

New Section

");

In this example, note that we did not bother to specify an input. We also included a template placeholder for rowclass, which is used to highlight alternating rows if you make use of that feature.

Preformatted rows cannot make use of other FormBuilder features such as validation.

Header and Footer HTML

You can also insert arbitrary HTML before and after the form, using:

    $f->set("head",$header_html);
$f->set("foot",$footer_html);

These HTML snippets will go outside the form tags.


CSS

The default template makes use of the following CSS classes:

form.Form
The entire form is given a CSS class of ``Form''.

p.A, p.B
Wraps the prompt and input field. The class A/B alternates if row highlighting has been enabled (see below), otherwise they are all class A.

span.prompt
Wraps the prompt string.

span.input
Wraps the input field(s). This may include sub-prompts such as radio button labels.

input.formButton
Used for the final form buttons only.

div.formButtons
Wraps the group of final form buttons.

Additionally, required fields may also use the following CSS classes:

span.requiredPrompt
Wraps the prompt string of a required field.

span.requiredInput
Wraps the input tag(s) of a required field.

span.missingPrompt
Wraps the prompt string of a required field if the form has been populated with data, but the required field has no data.

span.missingInput
Wraps the input tag(s) of a required field if the form has been populated with data, but the required field has no data.

Alternating Row Styles

Set alternate-row highlighting on as follows:

    $f->set("highlight_rows",1);

When using row-by-row templating, this will give each row of the form an alternating CSS class of ``A'' or ``B''. These can be used to give each row an alternating background color, as is commonly done for reports. This only works for row-by-row templates (including the default template).

If defining a custom row-by-row template, use the tag [[rowclass]] to insert the appropriate row class. For example, here is a tabular form layout that alternates the style of each table row:

    $f->template("",
"",
"
[[prompt]][[input]]
");

To ignore the row class entirely, simply do not reference it in your template.

Custom CSS

You can also specify custom CSS classes when building forms. The input method accepts a class attribute, which will be used to style the tags that it outputs.

You can also specify arbitrary CSS classes in your templates, instead of using the default classes described above.


FormBuilder and Form

The Form class also provides form-building functions, but it uses FormBuilder for the low-level features. Understanding the interaction between Form and FormBuilder can be useful when you want to make use of features from both form handling packages.

Form handles the mapping of database tables and columns to forms and form fields. ExSite::Form::form() returns a FormBuilder object, and that object is used by Form to assemble its auto-generated forms. If you call ExSite::Form::form() with no parameters, you will get the current FormBuilder object; if you provide parameters, it will discard the current object, and initialize a new one. When you invoke any of the Form functions like input_record, input_column, input_exsite, and input_html, the resulting form field is appended to appended to the current FormBuilder object. That means you can add autogenerated Form fields to the form using these functions, and also add customized fields to the form by using the FormBuilder object directly.

Example

Create a new FormBuilder object via Form so that you can add fields with ExSite database or datatype logic:

    $db = new ExSite::Form;
my $f = $db->form(method=>"post",action=>$action);

Add a database field to this form (ie. a field that maps to a particular database column):

    $db->input_column(table=>"my_table",
column=>"my_column",
value=>$val);

Add a field to this form that collects data for a special ExSite datatype:

    $db->input_exsite(datatype=>"datetime",
name=>"date",
value=>"2008-Jan-01 12:01:00");

Add a basic HTML input element that is required:

    $db->input_html(type=>"text",name=>"email",required=>1);

Or (equivalent to previous, going direct to FormBuilder):

    $f->input(type=>"text",name=>"email",required=>1);

Generate the form:

    my $form_html = $f->make();

Topics