OO::Form, initial release
Several months ago, inspired as I recall by Kate’s How to Avoid Writing Code article, I started playing with Class::DBI and Template Toolkit as a rapid development environment.
Not So Fast
Turns out rapid development isn’t really my style. I’m still futzing around with the earliest stages of functionality of my first app I started developing in this brave new way. Part of the problem is I get distracted into “doing things right”, which is about as antithetical to RAD as one can get. I’ve fallen into a number of these sorts of traps on this project, but a major one was the lack of a form class that integrated nicely into this dev. environment. After all you can’t really say you’re doing rapid web development, and then hand build, and process every form, can you? ### A Better Params Trap
CGI::FormBuilder is the current standard, and its very impressive in its way, but it didn’t meet my needs. In particular it does too much, and therefore plays poorly with other technologies. It was also totally unsubclassable. So with great pleasure I present OO::Form 0.01-alpha-experimental. (which has been kicking around for a week fews, but I finally added some POD)
A Curiosity
I don’t really expect anyone to want to use this code yet, but if you’re curious, and/or want to tell me what I’m doing wrong, take a look at it. It is currently incomplete as I’ve been fleshing out the features as needed, but what is in place, is working well (everything documented is working). Also if you’re really interested I’ll send you the code I’m working on that uses it, so you can see it in action. (beyond the example in the POD below) ### My Favorite Feature
Besides being all cool, and OO, and integrating well with Class::DBI, and TT2 (and presumably any other template system, but I wouldn’t know), OO::Form allows you to define arbitrary field types. So besides text, password, select, checkbox, and textarea (I’ve been adding the standards as I need them), OO::Form also ships with an OO::Form::Field::date, which does intelligent date, and timezone handling, and while currently displayed as 3 pull down menus, could be easily subclassed (just override the ashtml method) to display via a totally different widget. ### OO::Form – pod2html
NAME
OO::Form – an object oriented framework for building, displaying, and processing forms.
SYNOPSIS
package MyApp::AddWidgetForm;
use base ‘OO::Form’;
our $FIELD<em>PROFILE = {
fields =>
[qw(name widget</em>style note expiration<em>date) ],
types => {
widget</em>style => 'select',
note => 'textarea',
expiration<em>date => 'date'
},
options => {
widget</em>style => [qw(blue green chrome)]
},
required => [qw(name widget<em>style)]
};</em>
sub new {
my $class = shift;
my $self = $class->SUPER::new(@);
$self->add<em>fields($FIELD</em>PROFILE);
$self->submit<em>field('add</em>widget');
return $self;
}
….
(else where in your application)
my $form = MyApp::AddWidgetForm->new({query => $query, ...});
if ($form->is<em>submitted and $form->validates ) {
MyAdd::Widget->new</em>widget({
name => $form->name->value,
widget<em>style => $form->widget</em>style->value,
note => $form->note->value,
expiration-date => $form->expiration<em>date->value<br></br>
});
display</em>widget<em>added</em>confirmation();
}
else {
# form hasn't been submitted, or we encountered an error
display<em>page($template, { form => $form } );
}</em>
…,
(mean while, in a nearby template)
[% IF form.errors %]
<ul class="errors">
[% FOREACH e = form.errors %]<li>[% e %]</li>[% END %]
</ul>
[% END %]
Name: [% form.name.html(size => '40') %]<br />
Style: [% form.widgetstyle.html(default => 'blue') %] <br />
Expiration: [% form.expiration<em>date.html %]
Note: <br />
[% form.note.html(rows =>4, cols=>50) %]<br />
<input type="submit" name="[%form.submit</em>field%]" value="Add Widget" />
<p>...,</p>
DESCRIPTION
OO::Form
represents an HTML form as a collection of OO::Form::Field
objects, a state (is submitted), and some basic error handling. It was inspired by CGI::FormBuilder
, and by the difficulty I had integrating CGI::FormBuilder
into my application. The OO::Form
was designed to be used in conjuction with the Template Toolkit, and Class::DBI
though they aren’t tightly coupled.
The most straightforward way to use OO::Form
is to create your own subclass of OO::Form
for each form in your application. (though create and update pages can usually re-use the same form object, and for very simple forms, it is probably less useful)
Then pass a ‘fields profile’ to the OO::Form->add<em>fields()</em>
method which is a factory method for creating object of type OO::Form::Field and its varied subclasses.
USAGE
Field Profile
A field profile is a compact form of specifying all the fields in your form. (Alternately you could insantiate OO::Form:Field::*
objects one by one and add each one with $form->field($field))
A field profile is a hashref with 6 fields.
- fields
An arrayref of the names of fields to instantiate. Names must be unique, and they must not have the same name as any method of your form object (as they will be accessed by calling $form->$fieldname()) - types
A hashref of names mapped to their field types. Any field which is not given a type will be a generic field object, and will be drawn as a text field if their
html()
method is called.OO::Form::Field-
factory()> will be called for each field, and be passed its type. The defaultfactory()
method tries to instantiate an object of typeOO::Form::Field::$type
. You can subclassOO::Form::Field
to provide a whole set of new field types, or new behaviour for old field types. Additionally if anyone has suggestions for a smarter factory method, I’m interested.
Current types are the basic HTML fields: text, password, checkbox, textarea, select
Also the composite field type: date (represented as 3 pulldowns) is available.
See OO::Form::Field
for more detail
-
options For field types with options (selects aka pulldown menus, and radio/checkbox groups) values can be passed with the options field. A hashref of field names pointing to either an arrayref of options (when you want value and text to be the same) or a hashref of value to text mappings.
-
values A hashref of field name to value mappings. If you want your field to start preconfigured with certain values, pass in a values hash. If your values hashref contains a subclass of
Class::Accessor
(like aClass::DBI
object) then the objectsget()
method will be called, allowing forms to be rapidly populated from a database. -
required An arrayref of names of the required fields.
OO::Form-
validate> will return false (and populate an errors array) if any required fields are missing2 Error Handling and Validation
There are hooks for a future implementation of a full blown validation framework (probably using Data::FormValidator
).
In the meantime OO::Form
checks that required fields have been filled in, and that none of the various fields through their own error.
Calling the instance method validate()
(validates() is a synonym), returns true or false depending on whether there are any error conditions in the current form. It also populates and errors array, useful for re-displaying the form page with a list of errors.
A useful idiom for calling validates in demonstrated in the synops.
Besides the form wide errors array (accessible at $form->errors()
), each field can have their own error (though only one) accessible at $form->widgetstyle->error(), this is useful for re-displaying a form, with error messages inlined.
METHODS
OO::Form
is pretty straightforward, much of the complexity is actually in OO::Form::Field
. Almost all OO::Form
methods accept a hashref of named arguments.
Note this is all experimental.
new($params)
Construct a new form. Often you’ll want to override this with your own constructor. The default constructor looks for (and will supply defaults if it doesn’t find):query'',
submitfield”, “time_zone”. * query is a CGI.pm object, or something else which masquerades as one
* submitfield is the name of the button that submits this form (checked by issubmitted.
* time_zone is used by OO::Form::Field::date
to do intelligent localization of date/time values
Additionally in the future OO::Form::new
should take a locale.
add<em>fields($field</em>profile)
Take a field profile (as discussed in the previous section) an populate a form. -field(...)
A field accessor. Can take a hashref (or field object) and passes it toOO::Form::Field-
factory()>, and attach the results to the current form. Otherwise assume a string was passed in, and return the field with that name. (AUTOLOAD will callfield($field_name)
for unknown methods)