Advanced CGI::Application

Chapter 11 - CGI Application Modules for CPAN
by?Sam Tregar?
Apress ? 2002
has companion web siteCompanion Web Site

The use of CGI::Application shown previously is enough to accomplish a great improvement in CGI development. By abstracting your application logic into a series of run modes, you'll immediately get a cleaner and more comprehensible structure. This section will show how CGI::Application can be used to add further improvements.

Using Templates

In the state machine shown for the BBS application in Figure 11-5, the run modes are depicted as events connecting one state to the next. For each state there may be any number of run modes leading to and from the state. As you've seen already, run modes are implemented by individual methods in the CGI::Application subclass. How do you implement the states?

The common answer would be to use a series of calls to the CGI.pm's HTML- generation functions.[3] Listing 11-3 shows a simple version of how the message entry screen entered through the "new" and "reply" run modes might be implemented. Figure 11-6 shows the CGI in action. As you can probably see, this code has a serious flaw—the output is unbearably ugly. Fixing this would mean complicating the code with table setup, CSS attributes, images, and more. I've found that on a typical CGI project at least half the time is devoted to tweaking the HTML output to look just right. Worse, using this approach, the display code can only be modified by a Perl programmer (you!).

click to expand
Figure 11-6: Message entry screen in action

Listing 11-3: Message Entry Screen Implemented with CGI.pm

start example
 use CGI ':standard'; # the _edit method is called from the "new" and "reply" run-modes. sub _edit {   my $self = shift;   my %options = @_;   my $output;   # output message entry page body   $output .= start_html("Message Editor") .              h1("Enter Your Message Below") .              start_form .              "Name: " . textfield("name") . p .              "Subject: " . textfield("subject") . p .              "Message Body" . p .              textarea(-name => "body", -rows => 4, -cols => 40) . p .              submit("Save Message").              hidden(-name => "rm", -default => "save", -override => 1);   # include reply_id in hidden field if replying   if (exists $options{reply_id}) {     $output .= hidden(-name => "reply_id", -default => $options{reply_id});   }   # end form and html page   $output .= end_form . end_html;   # return output, as all run-modes must!   return $output; } 
end example

A better solution is to use a templating system. There are many templating systems available, most of which would meet this challenge with ease. However, CGI::Application includes support for HTML::Template,[4] and the two have been specially designed to work well together.[5] Listings 11-4 and 11-5 show how the exact same output could be generated using HTML::Template.

Listing 11-4: Message Entry Screen Implemented with HTML::Template

start example
 sub _edit {   my $self = shift;   my %options = @_;   # load template for edit screen   my $template = $self->load_tmpl('edit.tmpl');   # include reply_id in hidden field if replying   $template->param(reply_id => $reply_id) if exists $options{reply_id};   # return template output   return $template->output; } 
end example

Listing 11-5: Message Entry Screen Template File edit.tmpl

start example
 <html> <head>   <title>Message Editor</title> </head> <body>   <h1>Enter Your Message Below</h1>   <form method="post">     Name: <input type="text" name="name"><p>     Subject: <input type="text" name="subject"><p>     Message Body<p>     <textarea name="body" rows=4 cols=40></textarea><p>     <input type="submit" value="Save Message">     <input type="hidden" name="rm" value="save">     <tmpl_if reply_id>       <input type="hidden" name="reply_id" value="<tmpl_var reply_id>">     </tmpl_if>  </form> </body> </html> 
end example

Listing 11-4 shows the new _edit() method. Now all the code does is load the template using CGI::Application's load_tmpl() method. This method takes the name of an HTML::Template template file and calls HTML::Template->new() to load it, returning the new HTML::Template object. Next, if the user is replying to a message, the reply_id template parameter is set using HTML::Template's param() method. This method works similarly to the CGI.pm param() method, but instead of accessing CGI parameters it sets variables declared in the template with <tmpl_var>.

Finally, the template output is generated using the output() method and returned. Yes, it really is that simple!

Listing 11-5, the template file (edit.tmpl) used in Listing 11-4, is a bit more complicated, but you can blame HTML for that. It's unfortunate that no one thought to ask Larry Wall to design the markup language for the Web! However, since this is mostly plain HTML, there are many skilled designers available to take this stuff off your hands. The only part of this template that's not plain HTML is the section that optionally sets up the reply_id parameter:

     <tmpl_if reply_id>        <input type="hidden" name="reply_id" value="<tmpl_var reply_id>">     </tmpl_if> 

This section uses two of HTML::Template's special tags, <tmpl_if> and <tmpl_var>, to conditionally include the reply_id hidden field in the form. I don't have time to explore HTML::Template fully here—for that see the HTML::Template documentation.

The benefits of this approach are numerous. By separating the HTML from the code that drives the application, both can be made simpler. Furthermore, people with different skills can work more efficiently on the part of the application they know best. In this example, I could spend my time working on adding features to the BBS.pm file while an HTML designer works on making the form easier to look at and use. In addition, as I'll demonstrate later, a proper use of templating is critical to allowing your CGI applications to be reused by others.

Instance Parameters

Every CGI::Application needs at least two pieces—a module containing a subclass of CGI::Application and an instance script that uses that module. The example instance script in Listing 11-2 does nothing more than call new() on the BBS class and then run() on the returned object:

 my $bbs = BBS->new(); $bbs->run(); 

For simple applications this is all you'll need. However, it is possible to use the instance script to modify the behavior of the application.

One way CGI::Application provides configurability is through the TMPL_PATH option. When your application calls the load_tmpl() method to instantiate an HTML::Template object, the filename must be either an absolute path or relative to the current directory. But if you specify the TMPL_PATH option to new(), you can tell CGI::Application to look elsewhere for your templates. For example, if I wanted to keep the templates for the BBS application in /usr/local/templates, I could adjust the call to new() to look as follows:

 my $bbs = BBS->new(TMPL_PATH => "/usr/local/templates/"); 

This option could be used to deploy multiple instances of the BBS module with different designs coming from different template sets. Each instance script uses the same module, but the constructed objects will use different templates, and as a result the user will see a different design in his or her browser.

Aside from configuring CGI::Application, new() also includes support for application-specific parameters. Using the PARAMS option, you can specify a set of key-value pairs that can be accessed later by the application. For example, imagine that the BBS module was written to store its messages in a Berkley DB file accessed using the DB_File module. Each instance of the BBS will need its own file to store messages. This could be provided using the PARAMS option to new():

 my $bbs = BBS->new(PARAMS => { message_db => "/tmp/bbs1.db" }); 

Now in the BBS code this value can be accessed using the param() method on the CGI::Application object:

 use DB_File; sub list {   my $self = shift;   my $filename = $self->param('message_db'); # get the configured db filename   tie %db, "DB_File", $filename; # access it with DB_File   # ... } 

Note that the param() method offered by CGI::Application is different from the param() method offered by CGI.pm objects. CGI::Application's param() accesses configuration data from the instance script, whereas CGI.pm's param() accesses incoming CGI parameters. Be careful not to confuse the two—you don't want to start opening arbitrary files on your file system based on CGI input!

By making your applications configurable through their instance scripts, you'll increase your opportunities for application reuse. As I'll show you next, building a CGI::Application module for CPAN requires you to focus on configurability as a primary goal.

[3]Or possibly just a series of print() statements containing raw HTML. That's so ugly, I can't even bring myself to type up an example!

[4]I wrote HTML::Template while working for Jesse Erlbaum, the author of CGI::Application, at Vanguard Media (http://www.vm.com). It is, of course, available on CPAN!

[5]However, if HTML::Template isn't your tool of choice, using an alternative templating system is as easy as implementing a replacement for the load_tmpl() method in your subclass.



Writing Perl Modules for CPAN
Writing Perl Modules for CPAN
ISBN: 159059018X
EAN: 2147483647
Year: 2002
Pages: 110
Authors: Sam Tregar

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net