[Catalyst] Re: HTML::FormFu and Rose::DB::Object?

John Siracusa siracusa at mindspring.com
Wed Apr 4 15:06:32 GMT 2007


On 4/4/07 2:12 AM, Quinn Weaver wrote:
> Bill Moseley wrote:
>> On Tue, Apr 03, 2007 at 08:53:32PM -0700, Quinn Weaver wrote:
>>> In contrast to FormBuilder, RHTMLO wants you to write your HTML form
>>> by calling Perl methods, somewhat in the spirit of CGI.pm.  This makes
>>> it hard for design people to edit the form.
>> 
>> Maybe I'm not understanding that paragraph, but in RHTMLO (IIRC) you
>> can do [% form.field('foo').xhtml %] in a template.  What are
>> you thinking would be easier for the design people?
> 
> No, you're right.  I didn't catch that possibility, though it's
> implicit in the documentation.  Partly because the docs tell you the
> details of all the methods you can call, but not the big picture of
> what they're good for--in other words, they're etic, not emic.

Yeah, the docs could use a good tutorial showing typical usage.  Trouble is,
it's hard to pin down "typical" since people use RHTMLO in a variety of
ways.

> In contrast to FormBuilder, RHTMLO wants you to write your HTML form
> by calling Perl methods, somewhat in the spirit of CGI.pm.  This makes
> it hard for design people to edit the form.

While RHTMLO does produce its HTML pieces on demand through Perl methods, it
does so in a pretty standardized manner that lends itself to easy wrapping.
For example, here's a snippet of a "designer-friendly" Mason template
containing calls out to an RHTMLO form:

<% 'START_FORM' |fm %>

<table class="form">
<tr>
  <td class="label"><% 'USERNAME:LABEL' |f %></td>
  <td class="field"><% 'USERNAME:FIELD' |f %></td>
</tr>

<tr>
  <td class="label"><% 'FIRST_NAME:LABEL' |f %></td>
  <td class="field"><% 'FIRST_NAME:FIELD' |f %></td>
</tr>

<tr>
  <td class="label"><% 'LAST_NAME:LABEL' |f %></td>
  <td class="field"><% 'LAST_NAME:FIELD' |f %></td>
</tr>
<tr>
  <td class="label"><% 'EMAIL:LABEL' |f %></td>
  <td class="field"><% 'EMAIL:FIELD' |f %></td>
</tr>
...
<tr>
<td colspan="2">
  <% 'SAVE_BUTTON:FIELD' |f %>
  <% 'CANCEL_BUTTON:FIELD' |f %>
</td>
</tr>
</table>

<% 'END_FORM' |fm %>

This is an example of hiding "scary" Perl entirely from designers using
custom Mason escapes.  The syntax is pretty simple, roughly:

    (<form name>:)?<field name>:<method>

where <form name> defaults to "form" and there's a map from some common
RHTMLO method names to shorter <method> names.  (All names are folded to
lowercase.)  So, for example, this:

    <% 'EMAIL:LABEL' |f %>

internally becomes this:

    $form->field('email')->xhtml_label

This is just one example of what's possible, not necessarily The Way(tm) to
do things.  The point is just that the syntax and structure is regular
enough to allow it to be hidden behind a more designer-friendly syntax, if
desired.

On 4/4/07 12:53 AM, Bill Moseley wrote:
> On Tue, Apr 03, 2007 at 08:53:32PM -0700, Quinn Weaver wrote:
>> Also, Catalyst::Controller::FormBuilder accepts YAML as input,
>> whereas RHTMLO depends on Perl.  (Again, see below for a sample.)  YMMV,
>> but the design people here are relatively comfortable editing YAML and
>> quite uncomfortable editing Perl code.
> 
> Yes, I guess YMMV is true.  I think of what fields are processed by a
> form submission to be in the realm of the programmers, not the design
> people.  The form layout, on the other hand surely needs to be in the
> hands of the designers.
> 
> I find that each form often requires specific processing and
> validation that has to be accomplished in Perl anyway.  So, having a
> single Perl class that defines the form's fields along with any
> special validation code works nicely.

Yes, there's definitely a divergence in philosophy there.  I'm with Bill in
that I expect the details of the widgets to be the purview of the
programmer.  The designer chooses where those widgets go, perhaps even so
far as putting different pieces of the same widget (e.g., the label, the
field itself, the error message) in different places. And the designer
styles the widgets and surrounding according to usual CSS conventions based
on semantic markup.

This division makes even more sense when you realize that RHTMLO's idea of a
"widget" is significantly different than the traditional "single HTML form
tag" concept.  For example, a date picker made up of multiple HTML elements
and a JavaScript calendar pop-up would be a single widget in RHTMLO.

Yes, you could make such a thing out of a bunch of individual fields, with
the designer laying them out and adding the JavaScript calendar pop-up in
the appropriate place and so on, but that increases work for the designer
and (most importantly, as far as the RHTMLO philosophy is concerned)
decreases the potential for reuse.

The idea with RHTMLO is to build up a library of reusable (and potentially
complex) widgets.  Make that date picker once, according to a agreed upon
design and function for your current project (or entire company), and then
reuse it anywhere as easily as any other "simple" widget:

    $form->field('start_date')->xhtml

which might produce:

    +-------+  +----+  +--------+
    | Jan v |  | 12 |  | 2007 v | [c]
    +-------+  +----+  +--------+

That is, a pop-up for month, a text field for day, and a pop-up for year,
plus a [c] calendar pop-up icon.  Server-side, this widget would validate
the date and produce and set the appropriate error messages as needed.  And
to get the value of this widget?

    $dt = $form->field_value('start_date'); # datetime object

Later, if you decide to change this widget to be a single text field, you
just change the type of the field in the Perl form class.  The template
doesn't have to change at all.  The same incantation, "show the start_date
widget here", works no matter how complex that widget is.

> This does mean, however, that the design people have to come to you,
> the programmer, if they want changes to the form--e.g. if they want
> to change the contents of a <div> or, if, in plain HTML land, they want
> to change the type of an <input>.  (Obviously the problem is ameliorated
> in XHTML, since they can change some stuff using CSS.)  You can imagine
> the same thing happening for form-related Javascript.

If they want to change the widget type, yes, they have to consult a
server-side developer.  But everything else is up to them: rearranging
pieces, adding new wrapper HTML, all CSS, etc.

> Well... As you can see, my main goal is to support the common division of
> labor between XHTML (etc.) design and back-end coding.  That's what I have in
> mind when I think separation of concerns.
>
> I'm still searching for the right solution, but RHTMLO seems a little
> worse in this department than FormBuilder.  Or am I missing something?
> =)  Maybe with clean XHTML it doesn't matter.  Maybe.  I'm slated soon
> to run this idea by my client's design people and see what they think.

I think what you're missing is that RHTMLO offers significantly more
abstraction beyond the "single HTML element/single field" model: compound
fields, nested forms, input/output filters, inflate/deflate on the Perl
side, etc.  To support this level of abstraction, certain details of HTML
generation have to be moved into the widgets themselves.  But this is
considered a feature, not a bug! :)

How many times does a designer want to hand-lay-out a complex date range
picker widget?  It saves a lot of designer effort and debugging to be able
to treat all widgets equally, as if each one was as simple as a single text
field.  And it saves even more effort if/when the widget is changed to
another type and the template doesn't have to be changed at all.

Nested forms, the other big reuse feature of RHTMLO, mostly benefit the
server-side developer.  Let's say you have a person who may have multiple
addresses, and addresses are all stored in a single shared "addresses" table
in the database.  To make application development easier, you could create
an AddressForm that does everything needed to handle a single address:
validation, initialization with a RDBO-based My::DB::Address object, and
creation or update of a My::DB::Address object based on the form contents.

Next, create a PersonForm that does the same thing for records in the
"persons" table and My::DB::Person objects.  Finally, when it comes time to
make your single form that creates/updates a person and all his addresses,
use RHTMLO's nested forms feature to make a form that contains one
PersonForm and N AddressForms:

    package PersonWithAddressesForm;
    ...
    sub build_form
    {
      my($self) = shift;

      $self->add_form(person => PersonForm->new);

      foreach my $n (1 .. MAX_ADDRESSES)
      {
        $self->add_form("address$n" => AddressForm->new);
      }
    }

Now, getting the equivalent person with all his addresses is a simple matter
of delegation to already-written code in the sub-forms:

    sub person_with_addresses_from_form
    {
      my($self) = shift;

      my $p = Person->new;

      if(my $id = $self->field_value('person.id')) # update existing person?
      {
        $p->id($id);
        $p->load;
      }

      # The PersonForm knows how to get/update a My::DB::Person
      $p = $self->form('person')->person_from_form($p);

      my @addresses;

      # Each address form knows how to get/update a My::DB::Address
      foreach my $n (1 .. MAX_ADDRESSES)
      {
        push(@addresses, $self->form("address$n")->address_from_form);
      }

      $p->addresses(@addreses); # one-to-many RDBO relationship

      return $p;
    }

Again, this structure is ripe for automation via convention, which can
reduce the code size even further.

-John






More information about the Catalyst mailing list