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.
A typical usage is shown below.
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.
new()
parameters:
$f->method($get_or_post);
$f->action($url);
$f->enctype($encoding_type);
$f->name($name);
$f->template($all_in_one_template);
$f->template($top_of_form, $row_by_row_template, $bottom_of_form);
$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.
print $f->make();
$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 are passed to the input()
method in an %options
hash.
accept
attribute, used by some file uploads.
The following parameters accept true/false values:
(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.
The following parameters are also accepted, although they are not input attributes:
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.
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.
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.
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.
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.
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
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.)
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:
required=>-1
to mark the field as required, but let
the validation happen on the server
required=>1
.
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.
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.
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.
$f->template("",
"",
"
[[prompt]] | [[input]] |
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.
# 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]] |
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.
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.
The default template makes use of the following CSS classes:
Additionally, required fields may also use the following CSS classes:
tag(s)
of a required field.
tag(s)
of a required field if the form has been
populated with data, but the required field has no data.
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.
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.
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.
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();