Monday, March 8, 2010

HTML::FormHandler result object

Sometime last year I re-structured HTML::FormHandler so that the input and values that are produced by the form validation process are stored in separate result objects. There is an alternate method 'run' (as opposed to the usual 'process' method) to get these objects, or you can get them from a form after 'process'.

I expected that those who were interested in using this would materialize and provide input into what additional facilities/hooks were needed, but as far as I can tell nobody has actually used the feature. I think that it's a fair assumption that nobody actually knows about it, or can't figure out the point.

The standard way to use FormHandler is (simplified) something like:

    my $form = MyApp::Form->new;
    $form->process( params => $params, item => $item, ...);
    if( $form->validated ) {
     ....
    }

When using the the result object, the form acts like a kind of factory, and it looks like this:

    my $form = MyApp::Form->new;
    my $result = $form->run( params => $parsms, item => $item, ... );
    if( $result->validated ) {
       ...
    }

...and all the state is stored in the result objects, ideally leaving none left in the form

Currently result objects refer back to some attributes of the form object for rendering, since a number of rendering related definitions are stored there. It would be possible to decouple that, but then you'd end up having to define the validation and rendering aspects of the fields in two places.

In practice, the separation between static (form definition) information and ephemeral data is not nearly as clearcut as you might think. I find that people often want to change attributes that would probably be initially defined as static based on values that are passed in. It is possible to do this in a clean way, of course. But since the previous results are always cleared out at the beginning of each 'process' call, in practice the difference between using the form with 'process' and using it with 'run' aren't huge.

Nonetheless, I am sure there are people who have good use cases for this alternative architecture, and would love to have people pound on it and use it.

Sunday, February 21, 2010

creating a FormHandler form from a DBIx::Class result source

Someone regularly pops up in #formhandler looking for a way to automatically create a form from a DBIx::Class result source. After explaining how to create a fairly simple role that would do that, the people have always gone away and nothing ever came of it.

I have mixed feelings about the idea, myself. Sure, there's a theoretical benefit to not defining things in multiple places. But in practice, there's almost always differences in selection, presentation, etc, that mean that an automatically derived form is of surprisingly little use. At least in my experience. YMMV. It's awfully easy to take the list of column names and type "has_field 'column';"

But then the eleventeenth person asked about automatic form generation - and the yaks must have been particularly hairy this weekend...because I made a good start at shaving this particular one.

There is now a new trait: HTML::FormHandler::TraitFor::DBICFields, and a package to do the mapping from source info to field definitions: HTML::FormHandler::Model::DBIC::TypeMap. I'm pretty sure that it's not quite right yet, but at least a start has been made, and it should be easier for those that want this kind of functionality to polish, add, and tweak. For one thing, it doesn't handle relationships yet. FormHandler can create form fields out of a lot of the standard DBIx::Class relationships, yet it is often not what you would want to have happen automatically. So I think skipping relationships as a default is probably OK.

Anyway, here is how to create a form by looking at the source columns:

   my $book = $schema->resultset('Book')->find(1);
   my $form = HTML::FormHandler::Model::DBIC->new_with_traits( 
      traits => ['HTML::FormHandler::TraitFor::DBICFields'],
      field_list => [ 'submit' => 
          { type => 'Submit', value => 'Save', order => 99 } ],
      item => $book );

The field_list attribute is because I thought it was better to explicitly specify the submit field, rather than automatically create it. I could be argued out of it...

The interface is only a few hours old, so if you have thoughts about how this should work, this is the time to speak up, before somebody starts using it and it becomes harder to change. :-)

Speaking of dynamic forms... HTML::FormHandler is known for it's Moose-y sugar interface, but there are other ways to create a FormHandler form. Using a 'field_list' as a parameter to 'new' works fine to create a dynamic form (though there are things that are possible inside a class that just can't be done with that kind of interface):

my @select_options = ( {value => 1, label => 'One'}, 
       {value => 2, label => 'Two'}, {value => 3, label => 'Three'} );
my $args =  {
    name       => 'test',
    field_list => [
        'username' => {
            type  => 'Text',
            apply => [ { check => qr/^[0-9a-z]*/, message => 
                   'Contains invalid characters' } ],
        },
        'password' => {
            type => 'Password',
        },
        'a_number' => {
            type      => 'IntRange',
            range_min => 12,
            range_max => 291,
        },
        'a_select' => {
            type    => 'Select',
            options => \@select_options,
        },
        'sub' => {
            type => 'Compound',
        },
        'sub.user' => {
            type  => 'Text',
            apply => [ { check => qr/^[0-9a-z]*/, 
               message => 'Not a valid user' } ],
        },
        'sub.name' => {
            type  => 'Text',
            apply => [ { check => qr/^[0-9a-z]*/, 
                message => 'Not a valid name' } ],
        },
        'submit' => {
            type => 'Submit',
        },
    ]
};
my $form = HTML::FormHandler->new( %$args );

And of course you can use one of the rendering roles, HTML::FormHandler::Render::Simple, or the rendering widgets, or write your own renderer... Too Many Ways To Do Things (tm).