Solo has asked for the wisdom of the Perl Monks concerning the following question:

I find it an Annoyance of C::A that while the current state is abstracted with run_mode, there is no similar abstraction for moving between states. I have to manually code buttons, hyperlinks or hard-code the next state in a hidden field.

What I want is to include with the declaration of each run mode, the set of next user-selectable run modes. Then from that list, (nearly) automagically add submit buttons to the page.

I'm in the midst of writing a Web+DB app now (with C::A, H::T and CGI::FormBuilder). I've written a tiny subclass of C::A that addresses this Annoyance--at present only with C::FB in tow.

Here's what I came up with:

### in NextRMS.pm package CGI::Application::NextRMS; # I am module-naming challenged use base 'CGI::Application'; sub nextrms { my $self = shift; my %args = (@_); my %run_modes; my %nextrms = exists $self->{__NEXTRMS} ? %{$self->{__NEXTRMS}} : {}; for my $key (keys %args) { $run_modes{$key} = exists $args{$key}{handler} ? $args{$key}{handler} : $key; #little feature for lazy me $nextrms{$key} = exists $args{$key}{nextrms} ? $args{$key}{nextrms} : []; } $self->run_modes(%run_modes); $self->{__NEXTRMS} = \%nextrms; } 1; ### In "webapp.pm"... package WebApp; # use base 'CGI::Application::NextRMS'; push @ISA, 'CGI::Application::NextRMS'; use CGI::Carp qw/ fatalsToBrowser /; use CGI::FormBuilder; sub setup { my $self = shift; $self->start_mode('List'); $self->mode_param('_submit'); # compat w/ C::FB $self->nextrms( Add => { handler => 'add', nextrms => ['Cancel Add','Save'], }, Save => { handler => 'db_create', nextrms => ['List'], }, 'Cancel Add' => { handler => 'list', nextrms => ['Add'], }, List => { handler => 'list', nextrms => ['Add'], }, ); $self->{form} = CGI::FormBuilder->new( keepextras => 1, method => 'POST', name => 'myform', params => $self->query(), fields => [qw/ Test /], validate => {}, ); } sub main_form { my $self = shift; return "Runmode: " . $self->get_current_runmode() . "<br>Dispatched sub: " . (caller(1))[3] . $self->{form}->render( submit => $self->{__NEXTRMS}->{$self->get_current_runmode +()}, ); } sub list { my $self = shift; return $self->main_form(); # or a list_form() :P } sub add { my $self = shift; return $self->main_form(); } sub db_create { my $self = shift; return $self->main_form(); } 1; ### In "webapp.cgi"... #use WebApp; my $webapp = WebApp->new(); $webapp->run();

I didn't find anything lightweight like this in my searches, if anyone has seen similar, please share!

Ideas for better 'next run mode' abstraction and general methodology are appreciated!

TIA

UPDATE: Change first code comment

--Solo
--
Without precise calculations we could fly right through a star or bounce too close to a supernova and that'd end your trip real quick, wouldn't it?

Replies are listed 'Best First'.
Re: CGI::Application next run mode buttons?
by flyingmoose (Priest) on Apr 02, 2004 at 20:18 UTC
    IMHO, CGI::Application is a state machine for an application model and adding view code (buttons and links) to the model by subclassing CGI::Application is (questionably) a design flaw. Better to make another class and seperate your model and view.
      It boils down to which should be responsible for possible state transitions: the controller or the view?

      I think controller. Therefore, I need a way for the controller to tell the view which state transitions are allowed, in order for the view to correctly prompt the user. Since this needs to happen for every state (except an end state), I think it makes sense to put them in the run_modes(...) declaration. I'm looking for other ideas.

      How the view takes the 'possible next states' data and presents it doesn't matter to me. But the complicated part is each view component needs the data in a different format... H::T will need it one way, TT2 another, etc. I'm using the form rendering piece of CGI::FormBuilder at the moment for 'brevity' in my example (a bad choice I now see). I will create a .tmpl file and adjust my example to it.

      How to abstract the possible next states data and how to have 'plugin' support for different view components is what I hope to discuss. But I need to make a better case and a more detailed write up will be forthcoming ;)

      At least that's how I've been looking at it.

      --Solo
Re: CGI::Application next run mode buttons?
by dragonchild (Archbishop) on Apr 02, 2004 at 19:20 UTC
    So, in other words, you want something that combines POE and CGI::Application?

    ------
    We are the carpenters and bricklayers of the Information Age.

    Then there are Damian modules.... *sigh* ... that's not about being less-lazy -- that's about being on some really good drugs -- you know, there is no spoon. - flyingmoose

      From my understanding of POE, it's a lot bigger and heavier than I need. I don't want to rewrite the web server, just add buttons to a multi-page form. But I'll dig into POE some more, thanks!
      --Solo
Re: CGI::Application next run mode buttons?
by Solo (Deacon) on Apr 05, 2004 at 16:58 UTC
    So, I said I'd include an HTML::Template example of my madness. Here is the additional code (to work, it needs to be added to the root node's code):

    sub main_form { my $self = shift; # Format NEXTRMS the way H::T expects my @NEXTRMS = map +{NEXTRMS => $_}, @{$self->{__NEXTRMS}->{$self->get_current_runmode()} +}; $self->{template}->param(RUNMODE => $self->get_current_runmode(), HANDLER => (caller(1))[3], NEXTRMS => \@NEXTRMS, ); return $self->{template}->output(); } __DATA__ <html> <head> </head> <body> Runmode: <TMPL_VAR NAME=RUNMODE><br> Dispatched sub: <TMPL_VAR NAME=HANDLER> <form action="/cgi-bin/monktest.pl" method="POST" name="myform"> Test <input name="Test" type="text" /> <input name="_reset" type="reset" value="Reset" /> <TMPL_LOOP NAME=NEXTRMS> <input name="_submit" onClick="this.form._submit.value = this.value;" type="submit" value="<TMPL_VAR NAME=NEXTRMS>" /> </TMPL_LOOP> </form> </body> </html>
    This produces (practically) identical output to the CGI::FormBuilder code. But I have a couple problems

    • My run mode names appear on the submit buttons, so they need to look pretty and at the same time be unique. This is a problem for wizard-like interfaces with lots of Back & Next buttons across different pages. This needs to change.
    • With the present data structure, what I call 'run_modes' above, aren't really the states, but rather names put to state transitions. The 'handler' is more like the state.
    I think I need to turn the 'run_mode' structure inside-out from how it is now.

    Thoughts?

    --Solo
    --
    Well, I suppose I could hotwire this thing.
      Hi Matt,

      I like this solution much better than directly building the html in your code (albeit the html in your example is still technically in your code, it's easy enough to break it apart in production).

      I've not considered handling state via the controller although have seen other frameworks such as StateMachine::Gestiana (written by the author of the Uttu framework), which I think employ this method. You may want to check into how state is handled in these modules.

      Bear in mind that in your examples the state is still being set via an html form parameter. This parameter can therefore be overridden by the user when the form is submitted. You'll need to rethink your solution if you want to enforce the sequence. My solution is to check the form data at the beginning of each runmode using CGI::Application::ValidateRM. I can return the user to a previous state if they have not submitted valid/required information.

      In any case, I think that you ought to document this technique at the CGI App wiki. It looks like a viable alternative to manually setting the next state.

      Regards,
      William