Fellow Monasterians,

I've been working with CGI::Application for a few years now and thought it would be good to submit a tutorial for a simple, but practical application. So here's my draft for your review.

Why another C::A tutorial? A few reasons: 1) I learn by example, so I thought more code examples would be helpful, 2) I felt showing a more practical example might spur on beginners, 3) show more integration with HTML::Template, and 4) show a more structured example showing scalability.

I've heard it said, "If you want to learn something, teach it." I've relearned some of the fundamentals I've forgotten in rote day-to-day coding I do. So, this has been good for me.

Having said that, I plan to learn even more as the good monks weigh in on this. Thanks in advance for your help and comments.

—Brad
"The important work of moving the world forward does not wait to be done by perfect men." George Eliot

Update: Copied tutorial draft from scatchpad to this node per Moritz. Making edits per comments.

A Tutorial for CGI::Application

A contact form using HTML::Template

CGI::Application falls under the category of a framework. Frameworks enable developers to use shorter, more readable coding conventions, at least on the surface, that can be performing more complicated, and possibly combined tasks behind it. The result is faster development, more consistent style, and fewer errors, to name but a few. CGI::Application provides an addition model of mapping your Web application to "screens" (forms, etc.) and creating the functions to process by pages.

There are more robust frameworks (with more robust learning curves), like Catalyst and Maypole, and more recently, Jifty, but CGI::Application provides a very approachable, easy-to-use, low overhead framework for Web developers who like working in a less abstracted environment and a little closer to Perl itself.

If you are convinced that CGI::Application is the framework for you, you're in the right place. If you are still unsure, check out some additional rationale. Otherwise, let's dive in.

This tutorial does not cover the installation of CGI::Application, or any of it's growing number of plug-ins, but will show how you can use CGI::Application and HTML::Template to build one of the most common ancillary interactive applications on the Web: a contact form. Also, this tutorial is not meant as a placement for CGI::Application's well-written POD, which should be required reading.

A Word About HTML::Template

While this is not a tutorial for HTML::Template or HTML::FillInForm, the novice Web developer will see an example of how they can all work together with CGI::Application to create more useful, dynamic, and user-friendly applications.

Basic CGI::Application Concepts

  1. All CGI::Application applications are invoked by an instance script, which, in test case below, is named index.cgi. One of the advantages of using CGI::Application, is that it encourages "smart" URI's, e.g., http://www.acmecorp.com/contact/ The instance script can be named anything, but this author prefers some__directory/index.cgi to take advantage of the smart URI, and using the .cgi extension, reserving .pl) and .pm for the actual modules.
  2. A instance usually passes one run mode, which can be equated to the processing of one screen or Web page. For example, the first run mode might populate and display a form, and the second run mode might validate that form upon submission. If a run mode is not passed, a default run mode can be set at the top of the actual application (more on that in a minute).
  3. After the function is run in the application, the application must return something or to something: usually an HTML page through CGI::Application's redirect method, or in our case, an HTML::Template template using an output method.
  4. $self, or the object, is passed throughout the application and finally returned at the end. It can be called anything, but $self is common practice in the OO world.

The Layout

The locations of your files will vary depending on your server, e.g., you could be on a shared host and using something like /usr/home/foobar and /foobar/public_html/. The salient point is that the CGI::Application applications (server-side) are placed "out-of-reach" of the public Web directory (client-side).

Notes:

  1. One programming camp would advocate putting all of one's modules in a "lib" directory as opposed to the "myapps" directory shown here. However, this author prefers to think of "lib" as a place for Perl, CPAN modules, and those modules that will not be edited and part of the general operation of Perl itsel—background stuff.
  2. Also, there are times when templates can and should be placed outside of the document root—in a directory in "myapps" for example. However, one of the reasons for using a template system is to separate application code from presentation code. Therefore it might be best to keep the designers and HTML folks on the "client side" of things and not mucking around on the "server side." That is way the sample application shown below places the templates in the doc root.
/opt/foobar/myapps/---+ | | | | | Foobar_Super.pm | | | Common.pm | | | /Acmecorp/---+ | | | Contact.pm | | | /conf/---+ | | | acmecorp.conf | | /var/www/acmecorp/--+ | home.html | /contact/----+ | | | index.cgi | /templates/---+ | | | contact.tmpl | thankyou.html

Sample Application

CGI::Application, Plugin's, and HTML::Template

index.cgi (our instance script)

Notes:

  1. this instance script will pass a PARAM to the application
  2. in this example, we need to output the form that now in a .tmpl file. Though some browsers will actually display the .tmpl file without HTML::Template, we'll use an instance of our application to simply display the form.
  3. the instance script might be evoked by a text link:
    <a href="http://www.acmecorp.com/contact/">Contact Us</a>
  4. note that paths are relative to the location of the .tmpl file and not the instance script or application.
#!/usr/local/bin/perl -T use lib "/opt/foobar/myapps/"; use warnings; use strict; use Acmecorp::Contact; my $app = Acmecorp::Contact->new( PARAM => 'client' ); $app->run();

contact.tmpl ('rm' is our run mode and will be passed upon submission to our instance script '/contact/index.cgi')

<form action="/contact/index.cgi" method="post"> <input type="hidden" name="rm" value="s" /> <tmpl_if errors> <tmpl_loop errors> <p style="color: red"><tmpl_var error></p> </tmpl_loop> </tmpl_if> <p>Today's date: <tmpl_var today></p> <p>Name: <input name="name" type="text" value="" /></p> <p>Address: <input name="address" type="text" value="" /></p> <p>City: <input name="city" type="text" value="" /></p> <p>More info: <input name="more_info" type="checkbox" value="yes +" /></p> <p><input name="Submit" type="submit" value="Submit" /></p> </form>

Foobar_Super (a super class for CGI::Application applications)

Notes:

  1. because you may be creating several CGI::Application applications for other sites on your server, developing a super class eliminate repetition
  2. CGI::Application Plugin's integrates the modules they are associated with, negating the necessity of loading the native CPAN module
  3. cgiapp_init is run before anything in the application. In the super class you can read configuration files, set paths, set sessions, etc.
  4. the PARAM 'client' that was passed by the instance script is used by the super class to 'personalize' the instance.
package Foobar_Super; use strict; use warnings; use base 'CGI::Application'; use CGI::Application::Plugin::FillInForm(qw/fill_form/); use CGI::Application::Plugin::Config::Simple; use CGI::Application::Plugin::Redirect; use CGI::Application::Plugin::Session; use CGI::Application::Plugin::DBH (qw/dbh_config dbh/); use HTML::Template; #--- Start CGI::APP sub cgiapp_init { my $self = shift; #--- Set Paths $self->config_file( '/opt/foobar/myapps/' . ucfirst $self->param('c +lient') .'/conf/'. $self->param('client').'.conf');; $self->tmpl_path( '/var/www/' . $self->param('client') . '/template +s ); #--- Session $self->session_config( DEFAULT_EXPIRY => '+8h'); #--- Contact to DB $self->dbh_config( $self->config_param('db.host'), $self->config_param('db.user'), $self->config_param('db.pass'), {RaiseError => 1} ); } 1;

acmecorp.conf (a configuration file)

Note: read by CA_Super's cgiapp_init

#--- MySQL Server --- [db] host = DBI:mysql:foobar:localhost user = acmecorp pass = AKCgKYxc

Contact.pm (the actual application called by the instance script

Notes:

  1. in the method 'display', the CGI::Application convention for specifying a template is used: $self->load_tmpl
  2. in the first firing of the instance script, no run mode will be passed, so the application will use the default function defined by the $self->start_mod('d') in the setup method. This will simply output, or display, the template file containing our form.
  3. we also use the CGI::Application::Plugin::FillInForm convention for outputting the form with the fill-in-form values: $self->fill_form
package Acmecorp::Contact; use base qw(Foobar_Super Common); use strict; use warnings; use MIME::Lite; #load any extra modules needed use Date::Calc qw(Today); #--- SETUP Run modes sub setup { my $self = shift; $self->start_mode('d'); #if no run mode, use 'd' $self->mode_param('rm'); $self->run_modes( 'd' => 'display', 's' => 'save_form' ); } #--- Display sub display { my $self = shift; my $template = $self->load_tmpl( 'contact.tmpl', die_on_bad_params => 0 ); $template->param( today => sprintf( '%4d-%02d-%02d', Today() ) ); + return $template->output(); } #--- Process sub save_form { my $self = shift; my ( %sql, @errors, $error, $fifvalues ); ($sql{'name'}, $error ) = $self->validate( $self->query->param('nam +e') ); if ( $error ) { push @errors, ( { 'error' => 'Name'.$error } ); +} ($sql{'address'}, $error ) = $self->validate( $self->query->param(' +address') ); if ( $error ) { push @errors, ( { 'error' => 'Address'.$error } +); } ($sql{'city'}, $error ) = $self->validate( $self->query->param('cit +y') ); if ( $error ) { push @errors, ( { 'error' => 'City'.$error } ); +} $sql{'more_info'} = $self->query->param('more_info'); #if there are errors, return the form with original input and error + messages if ( @errors ) { my $template = $self->load_tmpl( 'contact.tmpl', die_on_bad_params => 0, ); $template->param( errors => \@errors, today => sprintf( '%4d-%02d-%02d', Today() ), ); for my $key ( keys %sql ) { $fifvalues->{$key} = $sql{$key}; #assign fill-in-form values } return $self->fill_form( \$template->output, $fifvalues ); } else { $self->record(\%sql); #record the input return $self->redirect('/thankyou.html'); } } #--- Record sub record { my $self = shift; my $sql = shift; my %sql = %{ $sql }; #we use CAP::DBH to connect to the DB and execute our SQL statemen +t my $stmt = 'INSERT INTO contacts (' . join(',', keys %sql) . ') VALUES (' . join(',', ('?') x keys %sql) . ')'; $self->dbh->do($stmt, undef, values %sql); } 1;

Common.pm (a module with common methods)

package Common;
sub validate { my $self = shift; my $to_check = shift; if ( $to_check !~ /^([\w ]+)$/ ) { return ( $to_check, " has invalid characters or is blank" ); } else { return $1; } } 1;

Summary

This tutorial has shown use of the basis tenents of using CGI::Application as an application framework:

  1. file layout and directory structure
  2. instance scripts
  3. run modes
  4. cgiapp_init and redirect
  5. integration of CGI::Application::Plugin's
  6. use of super class
  7. intro to the HTML::Template templating system

As always, you are encouraged to read the POD for CGI::Application and then take a look at the growing number of Plugins to see if CGI::Application can further streamline your coding process.

Other Resources

CGI::Application Wiki
Mailing List
HTML::Template Tutorial
Red Antiqua Tutorial
Using CGI::Application

Replies are listed 'Best First'.
Re: Draft of CGI::Application Tutorial for review
by dragonchild (Archbishop) on Jul 16, 2008 at 17:07 UTC
    Looks good, but you assume that your reader already wants to use CA. What if they are looking for both a tutorial and the rationale why they should shift from their current methods? Re: CGI::Application - why? may be be helpful here.

    My criteria for good software:
    1. Does it work?
    2. Can someone else come in, make a change, and be reasonably certain no bugs were introduced?

      dragonchild, yeah, I assume they will already be sold on C::A, so maybe I should just reference your excellent nodes if they are still unsure. My rationale section is a little weak, I admit, but more directed at coders like myself. Maybe the rationale part should be dropped altogether.

      —Brad
      "The important work of moving the world forward does not wait to be done by perfect men." George Eliot
        If you want to drop it, that's fine. At least reference good rationales and say something like "I assume you're have already decided. If you haven't, go here, here, and here."

        My criteria for good software:
        1. Does it work?
        2. Can someone else come in, make a change, and be reasonably certain no bugs were introduced?
Re: Draft of CGI::Application Tutorial for review
by moritz (Cardinal) on Jul 16, 2008 at 19:56 UTC
    bradcathey,

    It would be nice if you incorporated the tutorial in your root node, so the context of this thread is not lost if you decided to use your scratchpad for something else.

    I noticed several small typographic inconsistencies, for example you're mixing C::A with CGI::Application, and run mode with run mode. I also think that the "Basic C::A Concepts" paragraph uses too much bold font, and is thus uneasy to read.

    Apart from that the tutorial looks very fine, and after reading it I have the feeling I could start with a short CGI::Application script.

      moritz, thanks for your comments, I will incorporate them.

      However, I'm not quite getting what you mean by

      incorporated the tutorial in your root node, so the context of this thread is not lost if you decided to use your scratchpad for something else

      Sorry, I'm just not tracking completely.

      —Brad
      "The important work of moving the world forward does not wait to be done by perfect men." George Eliot
        If you use your scratchpad for something else in future, anybody looking at this thread won't know what we're all talking about. So please go and copy&paste it into your original Meditation.
Re: Draft of CGI::Application Tutorial -- RFC
by wfsp (Abbot) on Jul 17, 2008 at 13:33 UTC
    I made an attempt at something similar a couple of years ago but I think you’ve made a better go of it (at least you’ve had some replies :-).

    I agree with moritz though, and you should include it in your post. I also agree you could remove the bold font.

    Getting the file structure right is indeed key. Shouldn’t all your modules be under a lib dir? Perhaps two, one for base/common modules and separate lib dirs for each app with a corresponding structure below those for ‘helper’ modules much like Perl does it. Also you have a conf file in the same dir as a module. They might be more at home in a conf dir. There is no need for template files to be under your document root (fewer files here the better, imo) so perhaps a tmpl dir too under your app dir.

    Your base class has hard coded dirs for finding the cnf file and the tmpl files and you don’t specify any for your sessions. I think this is something that the instance script could usefully pass to the app module. The instance script already has a hard coded path to the lib so perhaps you could pass that and your base module could then find everything it needs (e.g. tmp, data, tmpl, sessions, cnf).

    I think there may be a missing closing and opening code tag just before the Common.pm code.

    Well done and good luck!

      Thanks for your comments, wfsp

      Shouldn’t all your modules be under a lib dir?

      I have been back and forth on a lib directory. It is expected, but for some reason I always equate lib with the Perl app itself, and CPAN modules and the like. But I'm open and want to do what's best for the tutorial.

      Also you have a conf file in the same dir as a module.

      I usually put a .conf file in it's own directory, but was trying to keep it simple. But to your point, see below.

      /opt/foobar/lib/---+ | | | | | Foobar_Super.pm | | | Common.pm | | | /Acmecorp/---+ | | | Contact.pm | | | /conf/---+ | | | acmecorp.conf | | /var/www/acmecorp/--+ | home.html | /contact/----+ | | | index.cgi | /templates/---+ | | | contact.tmpl | thankyou.html
      There is no need for template files to be under your document root

      The reason I do this: we are a design firm and my designers need access to the templates, but I don't want them mucking around on the app side. But to your point, I have done this in the past. What would be best for the tutorial?

      Your base class has hard coded dirs for finding the cnf file and the tmpl files and you don’t specify any for your sessions.

      Hmmmm, not sure how this would look for session. Can you give me an example?

      —Brad
      "The important work of moving the world forward does not wait to be done by perfect men." George Eliot
Re: Draft of CGI::Application Tutorial -- RFC
by scorpio17 (Canon) on Jul 30, 2008 at 21:27 UTC
    Some time ago I started a tutorial on how to use CGI::Application to create a simple login page. It never made it past the "request for comments" stage, but you can still find it here.