[Catalyst-commits] r9395 - in Catalyst-Controller-RHTMLO/trunk: . lib lib/Catalyst lib/Catalyst/Controller lib/Catalyst/Controller/RHTMLO

jgottshall at dev.catalyst.perl.org jgottshall at dev.catalyst.perl.org
Tue Feb 24 19:46:16 GMT 2009


Author: jgottshall
Date: 2009-02-24 19:46:15 +0000 (Tue, 24 Feb 2009)
New Revision: 9395

Added:
   Catalyst-Controller-RHTMLO/trunk/lib/
   Catalyst-Controller-RHTMLO/trunk/lib/Catalyst/
   Catalyst-Controller-RHTMLO/trunk/lib/Catalyst/Controller/
   Catalyst-Controller-RHTMLO/trunk/lib/Catalyst/Controller/RHTMLO.pm
   Catalyst-Controller-RHTMLO/trunk/lib/Catalyst/Controller/RHTMLO/
   Catalyst-Controller-RHTMLO/trunk/lib/Catalyst/Controller/RHTMLO/Action.pm
Log:
Initial import of rough draft code


Added: Catalyst-Controller-RHTMLO/trunk/lib/Catalyst/Controller/RHTMLO/Action.pm
===================================================================
--- Catalyst-Controller-RHTMLO/trunk/lib/Catalyst/Controller/RHTMLO/Action.pm	                        (rev 0)
+++ Catalyst-Controller-RHTMLO/trunk/lib/Catalyst/Controller/RHTMLO/Action.pm	2009-02-24 19:46:15 UTC (rev 9395)
@@ -0,0 +1,42 @@
+package Catalyst::Controller::RHTMLO::Action;
+
+use strict;
+use warnings;
+
+use base 'Catalyst::Action';
+use MRO::Compat;
+use Catalyst::Utils;
+
+__PACKAGE__->mk_accessors(qw/_config/);
+
+sub execute {
+    my $self = shift;
+    my ( $controller, $c, @args ) = @_;
+
+    $self->_setup_forms($c);
+
+    return $self->next::method(@_);
+}
+
+sub _setup_forms {
+    my ( $self, $c ) = @_;
+    my $cfg = $self->_config;
+
+    while( my ($name, $class) = each %{$cfg->{form_classes}} ) {
+        # setup form
+        $c->log->debug("Loading form '$class' as '$name'");
+        my $form = $class->new();
+        $form->params( $c->req->params );
+        $form->init_fields;
+
+        # put form in stash (first or only)
+        $c->stash->{ $cfg->{stash_name} } ||= $form;
+
+        # put form in stash under its name, in case of multiple forms
+        $c->stash->{ $cfg->{stash_hash_name} }->{$name} = $form;
+    }
+
+    return;
+}
+
+1;

Added: Catalyst-Controller-RHTMLO/trunk/lib/Catalyst/Controller/RHTMLO.pm
===================================================================
--- Catalyst-Controller-RHTMLO/trunk/lib/Catalyst/Controller/RHTMLO.pm	                        (rev 0)
+++ Catalyst-Controller-RHTMLO/trunk/lib/Catalyst/Controller/RHTMLO.pm	2009-02-24 19:46:15 UTC (rev 9395)
@@ -0,0 +1,251 @@
+package Catalyst::Controller::RHTMLO;
+
+use strict;
+use warnings;
+
+use base 'Catalyst::Controller';
+use MRO::Compat;    # to get $self->next::method() right
+use Catalyst::Utils;
+
+=head1 NAME
+
+Catalyst::Controller::RHTMLO - Catalyst Base Controller for Rose::HTML::Objects forms
+
+=head1 SYNOPSIS
+
+    package MyApp::Controller::Books;
+    use base 'Catalyst::Controller::RHTMLO';
+
+    # loads MyApp::Form::Book (which isa Rose::HTML::Form)
+    sub edit : Local Form('Book') {
+        my ( $self, $c ) = @_;
+
+        # form object is already init'ed with params and stashed
+        my $form = $c->stash->{form};
+
+        if ( $form->was_submitted ) {
+            if ( $form->validate ) {
+                # write to db or whatever
+            }
+            else {
+                # show errors or whatever
+            }
+        }
+    }
+
+    # display two search forms on same page
+    sub search : Local Form('ByAuthor,ByTitle') {
+        my ( $self, $c ) = @_;
+
+        if ( $c->stash->{forms}->{ByAuthor}->was_submitted ) {
+            # look up books by author
+        }
+        elsif { $c->stash->{forms}->{ByTitle}->was_submitted ) {
+            # look up books by title, duh
+        }
+    }
+
+=head1 DESCRIPTION
+
+This base controller glues Catalyst actions to form classes derived from 
+L<Rose::HTML::Form>, a component of John Siracusa's excellent L<Rose::Object> 
+framework. Unlike some other form-loading modules (see L</"PRIOR ART">), this 
+one does not include any mechanism for defining form structures; it merely 
+loads, instantiates, and initializes pre-written form classes for use in your 
+controllers. 
+
+In order to utilize a particular form in a particular Catalyst action, simply declare an attribute on the subroutine:
+
+    sub edit : Local Form('Book') { }
+
+This will ensure that MyApp::Form::Book is loaded and initialized, basically 
+equivalent to the following:
+
+    my $form = MyApp::Form::Book->new();
+    $form->params($c->req->params);
+    $form->init_fields;
+    $c->stash->{form} = $form;
+
+The namespace used to complete the form class name is 
+L<configurable|/CONFIGURATION>, or you can specify a full package name by 
+prepending a 'plus' sign:
+
+    sub edit : Local Form('+My::FormClasses::Book') { }
+
+To display more than one distinct form on a page, just list them all in the 
+attribute, delimited with commas or spaces: 
+
+    sub search : Local Form('ByAuthor,ByTitle,BySubject') { 
+        my ($self, $c) = @_;
+
+        $c->stash->{forms}{ByAuthor}->action($c->uri_for('/search/byauthor'));
+        $c->stash->{forms}{ByTitle}->method('GET');
+        $c->stash->{forms}{BySubject}->name('bytopic');
+    }
+
+(Note that you must put all the form names inside one set of quotes; I<DO NOT> 
+try to quote each individual form name. This is a limitation of perl5's 
+subroutine attributes.) The first form listed will still be stored in the stash 
+in the usual location; I<all> the forms (including the first) will be stored 
+under a separate stash key, in a hash keyed to the name used to load them.
+
+If for some reason you need to render the same form more than once on a page, 
+just list it again:
+    
+    sub search : Local Form('Search,Search') {
+        my ($self, $c) = @_;
+
+        $c->stash->{forms}{Search}[0]->name('search_0');
+        $c->stash->{forms}{Search}[1]->name('search_1');
+    }
+
+In this (weird but possible) case, the forms will be put into an arrayref in 
+the expected location. I haven't actually attempted to use this technique in 
+production, so I don't know what else you might have to do to make it work...
+
+=head1 CONFIGURATION
+
+You can override many defaults using Catalyst's configuration mechanism:
+
+    __PACKAGE__->config(
+        'Controller::RHTMLO' => {
+            form_attr       => 'HasForm',
+            action_class    => 'MyApp::Action::RoseForm',
+            stash_name      => 'formobj',
+            stash_hash_name => 'allforms',
+            form_prefix     => 'MyApp::RoseForm',
+        }
+    );
+
+=over
+
+=item C<action_class> (default 'Catalyst::Controller::RHTMLO::Action')
+
+If you want to add more functionality to the automatic form loading and initialization, you can create your own action class:
+
+    package MyApp::Action::RoseForm;
+    use base 'Catalyst::Controller::RHTMLO::Action';
+
+    sub execute {
+        my $self = shift;
+        my ($controller, $c, @args) = @_;
+
+        # load forms via base class
+        $self->next::method(@_);
+        
+        # do cool stuff
+        $c->stash->{form}->add_fields(
+            secure_token => {
+                type  => 'hidden',
+                value => $c->some_cool_security_token
+            }
+        );
+        return;
+    }
+
+=item C<form_attr>
+
+Default: 'Form'. Set this to alter the subroutine attribute used to indicate 
+one or more forms to be loaded by a given action, e.g.:
+ 
+    sub edit : Local HasForm('Books') { }
+
+=item C<form_prefix>
+
+Default: 'MyApp::Form' (using your app's actual name). Set this to the 
+namespace where your Rose::HTML::Form subclasses live.
+
+=item C<stash_hash_name>
+
+Default: 'forms'. Sets the stash key under which all forms for a given action 
+will be stored by class name.
+
+=item C<stash_name>
+
+Default: 'form'. Sets the stash key under which the first form for a given 
+action will be stored.
+
+=head1 PRIOR ART
+
+There are several other modules on CPAN that do similar things, many having 
+inspired this module in various ways.
+
+=over
+
+=item L<Catalyst::Controller::FormBuilder>
+
+Provided a lot of insight into how to trigger the form loading process with a 
+custom subroutine attribute. Based on L<CGI::FormBuilder> rather than 
+Rose::HTML::Form. 
+
+=item L<CatalystX::RoseIntegrator>
+
+Looks like it uses a CGI::FormBuilder-style config file to construct 
+Rose::HTML::Form objects on the fly, rather than having static subclasses. Also 
+seems to include direct model integration with L<Rose::DB::Object>.
+
+=item L<CatalystX::CRUD::Controller::RHTMLO>
+
+A component that enables use of Rose::HTML::Form objects with Peter Karman's 
+cool L<CatalystX::CRUD> API.
+
+=back
+
+=head1 SEE ALSO
+
+L<Rose::HTML::Form>, L<Rose::HTML::Objects>, L<Rose::Object>, 
+L<Catalyst::Controller>, L<Catalyst::Action>, L<Catalyst>
+
+=head1 AUTHOR
+
+Jason Gottshall <jgottshall att capwiz dott com>
+
+=head1 LICENSE
+
+This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
+
+=cut
+
+__PACKAGE__->config(
+    'Controller::RHTMLO' => {
+        form_attr       => 'Form',
+        action_class    => 'Catalyst::Controller::RHTMLO::Action',
+        stash_name      => 'form',
+        stash_hash_name => 'forms',
+    }
+);
+
+sub create_action {
+    my ( $self, %args ) = @_;
+
+    my $cfg = $self->config->{'Controller::RHTMLO'};
+    $cfg->{form_prefix} ||= $self->config->{'name'} . '::Form';
+
+    if ( my $formnames = delete $args{attributes}{$cfg->{form_attr}} ) {
+        # ensure at least one form name is provided
+        @$formnames = grep { $_ } @$formnames;
+        unless (@$formnames) {
+            die "Attribute '$cfg->{form_attr}' for action '$args->{reverse}' missing form name";
+        }
+
+        # validate and load form classes
+        foreach my $name (@$formnames) {
+            my $class =
+                ( $name =~ s/^\+// )
+                ? join( '::', $cfg->{form_prefix}, $name )
+                : $name;
+            Catalyst::Utils::ensure_class_loaded($class);
+            $cfg->{form_classes}{$name} = $class;
+        }
+
+        # pass config to action
+        $args{_config} = $cfg;
+
+        # set action class
+        push @{ $args{attributes}{ActionClass} }, $cfg->{action_class};
+    }
+
+    return $self->next::method(%args);
+}
+
+1;




More information about the Catalyst-commits mailing list