Beefy Boxes and Bandwidth Generously Provided by pair Networks
P is for Practical
 
PerlMonks  

Re: Review: CGI::Prototype

by hardburn (Abbot)
on Dec 02, 2004 at 15:11 UTC ( [id://411785]=note: print w/replies, xml ) Need Help??


in reply to Review: CGI::Prototype

I'm going to have to see C::P in actual usage before deciding, but I'm going to agree with merlyn that C::A doesn't quite go far enough. In fact, I've been working on some ideas for improving it, though I may just forgo that if C::P turns out to cover them.

Consider a single page in a bigger web application. When you get a hit, you need to get parameters, validate them, do something with them, and return the output. In C::A, all this has to be done within the single subroutine that defines the runmode. You could have your runmode being a simple dispatcher to other subroutines, but somehow this just doesn't satisfy me.

Also, IMHO, C::A doesn't support looping runmodes very well. For example, consider a database frontend for a club where the leader of the club enters each of the member's contact information. We can't predict the maximum number of members, so the entry page has two submit buttons: one indicating we have another member to fill in, and another for indicating we're done and should save all the information to the database.

Our app will have to figure out which button was hit and handle the data and output as needed. In C::A, there is really only one place you can put this logic: cgiapp_prerun(). If you have many runmodes that need this sort of functionalilty, your cgiapp_prerun() can start getting rather large.

I haven't studied C::P enough to know if it will handle this sort of thing better, but I suspect it will.

One other thing:

C::P requires the use of Template Toolkit. While TT is arguably the cadillac of templating technology, there are hundreds of reasons to stick with HTML::Template.

I was initially wary of this, too, as I prefer HTML::Template. Then I looked at C::P a bit closer and saw that TT wasn't being used as Just Another Templating System. Its features are an intrinsic part of how C::P is operating. I think it would be possible to divorce the two, but I think you'd lose a lot by using H::T instead. This may finally be the reason for me to start using TT more.

"There is no shame in being self-taught, only in not trying to learn in the first place." -- Atrus, Myst: The Book of D'ni.

Replies are listed 'Best First'.
•Re^2: Review: CGI::Prototype
by merlyn (Sage) on Dec 02, 2004 at 15:33 UTC
    We can't predict the maximum number of members, so the entry page has two submit buttons: one indicating we have another member to fill in, and another for indicating we're done and should save all the information to the database.

    ...

    I haven't studied C::P enough to know if it will handle this sort of thing better, but I suspect it will.

    Yes. The basic theory is that there's an app-wide "dispatch" that figures out what state you are in. Then a specific "respond" gets called, which can notice which button was pressed, and return whether to stay on the same page (return $self), or go to a new page (return $self->nametopage("next")). Whichever page gets selected for rendering then does the render thing.

    To me, this makes a lot of sense in practice. When you're editing a given page, you have one editor window open on the .tt file, which describes how it looks and what fields it returns, and another window open on the .pm file, which describes what to do with those fields and what to do next. You also add in that same file extra methods for supplying your template with data while that page is being rendered. It's all quite natural and flexible.

    And groups of pages can have common ancestors, below the top level app, so the nested hierarchy and "two level" dispatch is there through the proper use of SUPER. It's just objects. Objects work.

    -- Randal L. Schwartz, Perl hacker
    Be sure to read my standard disclaimer if this is a reply.

Re^2: Review: CGI::Prototype
by Joost (Canon) on Dec 02, 2004 at 15:46 UTC
    Our app will have to figure out which button was hit and handle the data and output as needed. In C::A, there is really only one place you can put this logic: cgiapp_prerun(). If you have many runmodes that need this sort of functionalilty, your cgiapp_prerun() can start getting rather large.

    Why would you not have the runmode method handle this?

    I tend to use the runmode more as a vague indidactor of the expected action anyway - it determines the kind of checks that need to be done on the input, then decides which action to perform, and then refers to an output routine. Example:

    sub make_report { my $self = shift; if (my $missing = $self->missing_fields()) { # back to submitted form, with missing fields highlighted return $self->make_form($missing); } # make confirmation screen. $self->template( 'confirm.tmpl' => $self->form_values, ); }

    This decides the output based on wether or not all data has been filled in correctly, but it isn't really very different if you check for a button instead.

    update: after reading this thread again, it seems most some people find it hard to wrap their head around the following:

    runmode != page != template

    A runmode is just a hint from the calling form about what the input data is about, and what the application should to with it. It does not describe what template should be shown.

      The update here is interesting and may get at the difference of approach between the two modules. (Caveat -- while I know C::A, my exposure to C::P is limited to this thread so far and a quick POD skim, so I may have it wrong. Apologies to the authors if so.) Both modules are effectively just state machines. However, the state-less nature of web-browsing means that tracking state and state transition is a bit more difficult.

      The C::A approach with runmodes takes the approach that the runmode specifies a target -- a desired state, or really, a desired action which is usually to reach a certain state. The application reads the desired action from a parameter and dispatches to a subroutine for that action. The subroutine checks the validity of the action given the input and presents a new state back to the user. (This may be done within the runmode subroutine or as a call to a "display_x" subroutine.) Templates really define a state and logic in the templates specialize the state for special cases (e.g. redisplay a form with errors in red). Plus, templates define the the valid state transitions (runmodes) that can be called from a given state.

      The C::P approach instead uses the information submitted to the application to determine the state the user was just in rather than the target. (Because of the state-less web, it can't assume that the new request originated from the last state displayed.) The state corresponds to a class, which examines the input to determine an appropriate state transition (from among valid state transitions for that state) and then calls a subroutine in a new class (state) to display back to the user. Classes define how to enter a state -- by displaying a template -- and how to exit -- by executing a state transition for a given input.

      Syntax and templating systems aside, this conceptual difference seems to me to be the crux of the issue and preference for use may depend on how a programmer likes to think about it. In C::A, one defines state transitions and uses those to determine a new state to display. In C::P, one defines states and how a user gets from that state to other valid states.

      Polymorphism becomes important in C::P as a way of handling special cases (e.g. a form with errors) without repetitive coding, whereas with C::A, the special case coding would more likely happen procedurally in the templates.

      Hope that helps some people. (Just writing it was helpful to me to bend my mind around how to conceptualize states and transitions for CGI in general.)

      -xdg

      Code posted by xdg on PerlMonks is public domain. It has no warranties, express or implied. Posted code may not have been tested. Use at your own risk.

        I think that's an excellent summary of C::P, and my understanding of C::A as well.

        With C::P, you define a view and its associated controller. (I got this model from Smalltalk many decades ago.) The controller presents the view, which the user interacts with, which the controller then decides what to do next: activating another controller, or staying in the same view. Of course, controllers share a lot of code, and that's where the class hierarchy comes in. And we need a meta controller to handle the stateless nature of the web to select which controller is active, and a piece of meta information (the "state") to help with that.

        Thus, I believe (other than the commitment to Template Toolkit, which can be overridden with a single method override, and the inclusion of a CGI.pm object for field interpretation) that C::P is the most generic framework you can create. So, based on that most generic structure, I have one concrete derivative that deals with using a hidden field (C::P::Hidden), but I suspect there will be others.

        I also have a nice testing tool, which nobody has talked about, C::P::Mecha, which allows WWW::Mechanize to "run" the application, but in the same process without a webserver. This alone is worth the price of the download. {grin}

        -- Randal L. Schwartz, Perl hacker
        Be sure to read my standard disclaimer if this is a reply.

        So let me see if I've got this. State machines basically follow this cycle, after an initial setup:
        1. Look at your state and look in a table for what to do.
        2. Do stuff, probably depending on input, and figure out what state to go to next.
        3. Transition to the next state
        Now both C::A and C::P maintain state information by telling the browser "when you submit this form back to the server, come back in as state X". This is pretty much driven by the way HTTP works (the other choice is to use session ID variables and database lookups, but that tends to lead to un-web-like applications), so it's not surprising that they look alike in that respect. The difference is where in the state machine cycle the browser comes in. C::A seems from your description to put the browser into the last step; its state machine loop would be:
        1. Look at your state and look in a table for what to do.
        2. Do stuff, probably depending on input, and figure out what state to go to next.
        3. Tell the browser to come back in as the next state.
        4. Wait for the browser
        Whereas C::P seems to be:
        1. Look at your state and look in a table for what to do.
        2. Do stuff, and tell the browser to come back in as the second half of this state.
        3. Wait for browser
        4. Do some more stuff, probably depending on input, and determine what the next state should be.
        5. Transition to the next state
        Now, from just this description I'd expect the two application frameworks to be equivalent in the sense that an application could be switched between them by renaming some states and shuffling a bit of logic. The discussion so far would suggest that there are stronger differences. I'm not sure from this description which approach makes it easier to develop and maintain applications. The C::P approach seems more in line with what I'd want to do in typical data-entry or shopping cart web applications, but I'm wary of dismissing the C::A approach just because it doesn't immediately align with my current thinking. Lots of very useful tools don't align with my first impression of the problem domain.
        -- @/=map{[/./g]}qw/.h_nJ Xapou cets krht ele_ r_ra/; map{y/X_/\n /;print}map{pop@$_}@/for@/

        >Polymorphism becomes important in C::P as a way of handling special cases (e.g. a form with errors) without repetitive coding. Whereas with C::A, the special case coding would more likely happen procedurally in the templates.

        In C::A, when I have common processing I place it in a .pl file and require it in the proper modules. If I were better at OO, I guess I could make it a class and reference it similarly to what you describe for C::P.

        Do you know, is there a significant performance hit for doing everything in a C::P object model compared to C::A's model which (at least the way I use it) is more procedural programming within a basic framework of objects? Would this be a consideration for higher volume sites?

        Mark

      A runmode is just a hint from the calling form about what the input data is about, and what the application should to with it. It does not describe what template should be shown.

      Exactly my philosophy when working with C::A. I tried to factor this into a plugin that makes it very easy to just show templates (by using path_info to figure out which template to show and having associated data files for the templates so that they can "render themselves") so that the runmodes can concentrate on doing "the business logic".

Re^2: Review: CGI::Prototype
by Purdy (Hermit) on Dec 02, 2004 at 15:30 UTC
    Our app will have to figure out which button was hit and handle the data and output as needed. In C::A, there is really only one place you can put this logic: cgiapp_prerun(). If you have many runmodes that need this sort of functionalilty, your cgiapp_prerun() can start getting rather large.

    When I do this kind of thing, I put the logic in the handling runmode ... something like:

    # This runmode show the data-entry template sub show_entry { # ... yada yada yada } sub process_entry { my ( $self, $query ); $self = shift; $query = $self->query(); # This method will add the entry in the db $self->add_entry(); # Now if the user hit the 'add another' button... if ( $query->param( 'addanother' ) ) { return $self->show_entry(); } else { # mainscreen() being the main/home page runmode return $self->mainscreen(); } }

      When I do this kind of thing, I put the logic in the handling runmode

      I don't think this will work very well. Often, mainscreen() itself took input from some other form. Now I'll need to pile on yet more logic inside mainscreen() to know which runmode it's being called by.

      I don't see a solution with C::A that doesn't involve pileing on a lot of additional logic. It seems very good for applications that have a simple, linear progression through the pages (start -> A -> B -> C -> finish). It breaks down fast for more complex structures.

      "There is no shame in being self-taught, only in not trying to learn in the first place." -- Atrus, Myst: The Book of D'ni.

        /I'm assuming mainscreen() is just a call to a template in my response here...

        Why would mainscreen() change its appearance a lot based on where it was called from? It shouldn't really care - it should show data and messages and that's it. Use a different template for different screens, and include the common parts.

        I don't think this will work very well. Often, mainscreen() itself took input from some other form. Now I'll need to pile on yet more logic inside mainscreen() to know which runmode it's being called by.

        Well, it depends on your mainscreen(). My mainscreen() is always the very first page, which by definition doesn't take any input.

        In my years of cgiapp development, I actually haven't needed it any other way. That's probably b/c I don't develop the more complex structures, but I've done countless e-commerce and complicated database applications in this "simple" manner.

        Now that I think about it, there are a few other options. Remember in each runmode you got access to the $self (cgiapp class), which you can call its get_current_runmode() to see what runmode it's being called by. You also have access to the CGI object within. You can also stuff/access something inside the cgiapp object with its own param() method. Lastly, you can also pass along arguments to the various runmodes.

        You have to put that logic somewhere... ;)

Re^2: Review: CGI::Prototype
by dragonchild (Archbishop) on Dec 02, 2004 at 15:28 UTC
    Our app will have to figure out which button was hit and handle the data and output as needed. In C::A, there is really only one place you can put this logic: cgiapp_prerun(). If you have many runmodes that need this sort of functionalilty, your cgiapp_prerun() can start getting rather large.

    Why do both buttons go to the same runmode when they don't do the same thing? I would have the first button have a small Javascripty-doo that changes the hidden runmode field from ActuallySubmit to AddAnother, then submit. Then, you let C::A do the work of determining which button was hit using its native dispatching mechanism. No need for any cgiapp_prerun() nonsense. You're not letting C::A do what it's supposed to do.

    When you get a hit, you need to get parameters, validate them, do something with them, and return the output. In C::A, all this has to be done within the single subroutine that defines the runmode. You could have your runmode being a simple dispatcher to other subroutines, but somehow this just doesn't satisfy me.

    There are dozens of plugins that do exactly what you're trying to have the runmode do. Form validation, parameter getting, etc ... all of these are solved problems.

    I'm going to agree with merlyn that C::A doesn't quite go far enough.

    Please elaborate further - WHAT doesn't C::A do?

    Being right, does not endow the right to be rude; politeness costs nothing.
    Being unknowing, is not the same as being stupid.
    Expressing a contrary opinion, whether to the individual or the group, is more often a sign of deeper thought than of cantankerous belligerence.
    Do not mistake your goals as the only goals; your opinion as the only opinion; your confidence as correctness. Saying you know better is not the same as explaining you know better.

      I would have the first button have a small Javascripty-doo that changes the hidden runmode field from ActuallySubmit to AddAnother, then submit.

      dragonchild, I've never used C::A (or C::P), but why must the runmode be in a hidden field? Why not have the submit button be the runmode value? For example:

      <form action="blah" method="post"> <input type="text" name="data" value="Default data." /> <input type="submit" name="runmode" value="Save" /> <input type="submit" name="runmode" value="Add more data" /> </form>

      If the "Save" button is clicked, the "Save" runmode will be called. If the "Add more data" button is clicked, that runmode will be called.

      The only limitation is that either your button text or runmode names will be ugly.

        It's not completely bad since you can have the same form submit to different run modes with the same data without having to do some javascript hacking, but it does have it's down side...

        If you change the wording on the button (to be more user friendly, etc) then you also have to change the perl code on the backend to have that same name as the run-mode. not horribly difficult, but it means that a purely design change would need a change to the perl.
        I've been doing that for quite some time. The CGI::Application concept of runmodes (state transitions) seems to work best if there is only one runmode the user can go to next. In my case, there are multiple buttons on the page, each of which needs to transition to a different runmode. So, in my CGI::Application::setup routine, I define a hash that shows the mapping of how to do the transitions. Transitions depend not only on the name of the submit button but a hidden parameter that holds the last runmode (e.g. where we came from) since different runmodes could use the same button name to do different things. For example, I might have this in my setup routine. Note, I find the term "runmode" non-intuitive so in my applications I use the term "state".
        $self->mode_param(\&which_state); $self->{DISPATCH_SUBMIT} = { 'state_main' => { ' Create new question ' => 'state_new_question', ' View all questions ' => 'state_view_all', }, 'state_new_question' => { 'Add question' => 'state_add_question', 'Cancel' => 'state_main', } }
        The first entry in the hash is the the state the user just came from. The next level in the hash is the name of the submit button and then what runmode subroutine to call. Then in my application, there's a method called which_state which looks at the last state and decides what to do next. If a runmode has only 1 possible transition, then it can set the runmode (state) value directly and bypass this mechanism. This has worked quite well for me. A possible downside is that the name of the submit button (which some might argue is content) is embedded in the code so a change to the page requires a change to the code. In my case, I manage both content and code so I have not been hindered by this.
        the runmode is determined by a CGI parameter, usually named 'rm'. Because of that, it usually is a hidden input called 'rm' with the name of the next runmode as its value.

        The Submit buttons sound like an interesting idea ... There are a few interesting quirks to it, but it looks like it could be made to work. ++!

        Being right, does not endow the right to be rude; politeness costs nothing.
        Being unknowing, is not the same as being stupid.
        Expressing a contrary opinion, whether to the individual or the group, is more often a sign of deeper thought than of cantankerous belligerence.
        Do not mistake your goals as the only goals; your opinion as the only opinion; your confidence as correctness. Saying you know better is not the same as explaining you know better.

        >I've never used C::A (or C::P), but why must the runmode be in a hidden field? Why not have the submit button be the runmode value?

        That's what I do in C::A. My POST url looks something like "/cgi-bin/profile?area=acc" to signify that the form is submitted to the "profile" script, and is intended to access the sub-page "account settings." I suppose "area" is a run-mode. But, I go further, using the submit button's value to internally choose a runmode. My C::A code looks something like
        if (defined($q->param('area'))) { if ($q->param('area') eq 'acc') { if (defined($q->param('change_psw'))) { $self->start_mode('21'); } # end form to change password was submitted elsif (defined($q->param('change_acc'))) { $self->start_mode('22'); } # end form to change account settings was submitted elsif (defined($q->param('send_verify'))) { $self->start_mode('23'); } # end form to verify email address was submitted else { $self->start_mode('2'); } # end there was no submit, profile-account page should be displa +yed } # end profile area = account else { $self->start_mode('1'); } # end no profiel area was provided, profile main page should be disp +layed
        The idea here is that the "area" takes me to a specific sub-page for "profile" (account settings, my personal info, description, photos). If area is not set (or unintelligible), the main profile page is displayed (as if it's an initial display). If an area is specified, then if submit buttons (there may be more than one form on a page) set the run mode to process whichever was pressed. If no submit button was pressed, it's an initial display of the page (for that area). It works well for me. It gets ugly with long nested if/elsif structures. Followed by a long self->run_mode hash associating the numeric codes to the subroutines. I can see that CGI::Prototyle could make this more natural through inheritance. But, I like being able to see all the details and understanding how it works. OO is elegant but I still have trouble thinking in those terms.

        Mark
      Please elaborate further - WHAT doesn't C::A do?

      Well, it hardly does anything at all, wouldn't you agree? It's just a dispatch table. All of the interesting stuff is in plugins to add Data::FormValidator and friends. That's not a negative to me. I think most people are going to build more on top of C::A if they use it for anything significant.

      I would have the first button have a small Javascripty-doo that changes the hidden runmode field . . .

      Where I work, we have a significant number of users with JavaScript turned off (and it's staying off, as a lot of them are government users in departments with standing policies of not having JavaScript on).

      "There is no shame in being self-taught, only in not trying to learn in the first place." -- Atrus, Myst: The Book of D'ni.

      You can set the runmode via the submit button provided you make the value of your run mode "human readable". Something like:
      <input type="submit" name="rm" value="Update users" >
      So no javascript required.
Re^2: Review: CGI::Prototype
by Anonymous Monk on Dec 03, 2004 at 20:24 UTC
    I haven't looked at Merlyn's module yet, but I will.

    CGI::Application can be quite painful for making lots of apps do different things, and I didn't like it. I wrote my next app using RubyOnRails because of my experience with CGI::Application. Yes, C::A was a huge improvement over raw CGI when I first found it, but it still made coding un-fun and felt a little raw.

    There is NEVER anything wrong with reinventing a wheel when you think it's square, after all, you personally have to use that wheel. I am a *huge* proponent of clean code and good design ... wherever it can be found. Honestly though, I doubt I'm leaving rails anytime soon.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: note [id://411785]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others perusing the Monastery: (4)
As of 2024-03-28 08:40 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found