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 default factory() method tries to instantiate an object of type OO::Form::Field::$type. You can subclass OO::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 a Class::DBI object) then the objects get() 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 missing

    2 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 to OO::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 call field($field_name) for unknown methods)