Copyright Notice
This text is copyright by InfoStrada Communications, Inc., and is used with their permission. Further distribution or use is not permitted.This text has appeared in an edited form in Linux Magazine magazine. However, the version you are reading here is as the author originally submitted the article for publication, not after their editors applied their creativity.
Please read all the information in the table of contents before using this article.
Linux Magazine Column 72 (Jul 2005)
[Suggested title: ``Introduction to CGI::Prototype (part 3)'']
In the last two column articles, I introduced my CGI::Prototype
generic controller framework. Let's continue the examination with a
description a real workhorse subclass, CGI::Prototype::Hidden
.
The CGI::Prototype
framework can be used to write applications, but
lacks the concrete means by which the state of the application can
be deduced. I wrote the code expecting that various strategies for
state dispatch (hidden fields, cookies, server-side databases, mangled
URLs) would be coded as subclasses of CGI::Prototype
. The first of
these coded is hidden fields, as CGI::Prototype::Hidden
.
For CGI::Prototype::Hidden
(which I'll abbreviate CGIPH from here
forward), the state must be a simple arbitrary keyword: something that
matches /^\w+$/
. The state is used to select a particular class
for the controller, along with the default template for that class.
Once a state has been selected, the corresponding class is loaded into
the program (unless already defined), and used to determine the next
step in the program. This ``lazy loading'' behavior permits a minimum
of loading for CGI programs: at most, two different state-named
classes are loaded per web hit.
CGIPH also defines a ``wrapper'' template to provide common definitions, headers, and footers for the processed templates, and a means by which the templates can be found ``next to'' the class source files. More on those later.
CGIPH is designed to make multi-page web applications easy to create
and update. I found in practice that this is true: I recently created
a fully functional administrative application for the booking engine
of geekcruises.com
, in far less time than I suspect it would have
taken using hand-rolled CGI.pm code.
CGIPH makes extensive use of defaults, which generally work nicely,
but can be easily overridden for custom configurations. For example,
the dispatcher needs to know the name of the CGI parameter in which
the state will be stored. The default value is _state
, but if this
name conflicts with something else in your application, you can
override the value by simply defining a method named
config_state_param
in your application class with the new value.
For example:
package My::App; use base CGI::Prototype::Hidden;
sub config_state_param { "--state--" }
Now the state parameter will be in the --state--
parameter, not the
_state
parameter. Speaking of My::App
, the
config_class_prefix
(default My::App
) gets prepended to the
state to select the page class, so a state of enter_serial_number"
will map to the class of C<My::App::enter_serial_number>
.
If no state parameter exists, the config_default_page
state is used
instead. This configuration parameter defaults to welcome
. Unless
you override this parameter, your first hit will go to
My::App::welcome
as a class, which defaults to showing
My/App/welcome.tt
as a template.
This template is wrapped by a wrapper template, defaulting to
My/App/WRAPPER.tt
, but overridden by defining config_wrapper
.
The purpose of the wrapper is to provide definitions for common blocks
and variables, and common headers and footers. The most trivial
wrapper is simply:
[% PROCESS $tempate %]
which then runs the corresponding individual page template, however you will probably want something more along the lines of:
[%- TRY; content = PROCESS $template; self.CGI.header; self.CGI.start_html; content; self.CGI.end_html; ### exceptions ## for errors: CATCH; CLEAR; self.CGI.header('text/plain'); -%] An error has occurred. Remain calm. Authorities have been notified. Do not leave the general area. [%- FILTER stderr -%] ** [% template.filename %] error: [% error.info %] ** [% END; # FILTER END; # TRY -%]
This wrapper processes the individual page template, then ``wraps'' it with CGI HTML headers and footers, so that the page is properly displayed in the browser. This wrapper also traps any thrown errors, either from Template Toolkit or something called by a template, displaying an innocent message to the user and logging the details into the web error log.
One interesting trick here is to set variables in the template, and then check them in the wrapper. For example, if the template contains:
[% has_custom_header = 1 %]
then we can use that to alter the behavior of the wrapper:
[% content = PROCESS $template; IF has_custom_header; content; # it's all up to you ELSE; self.CGI.header; self.CGI.start_html; content; self.CGI.end_html; END; %]
This works because the template's variables are included in the wrapper's invocation context, allowing a two-way sharing.
So, the minimum CGIPH app consists of the CGI script in the CGI bin
area, definitions for the classes My::App
and My::App::welcome
,
and templates for welcome.tt
and WRAPPER.tt
. Each additional
state requires a pair of files: the Perl module to implement the Perl
code, and the template file to implement the view.
By default, the templates are placed in the same directory as the Perl
modules. This is because the default engine parameters put the Perl
@INC
array into Template's search path. If you want a different
policy, feel free to override engine_config
from the value given in
the CGIPH manpage with your own INCLUDE_PATH definition.
In order for the ``hidden'' part of CGIPH to work, every form or link
must maintain the state of the application. The easiest way I've
found to manage the state passing is to create a TT block that's
meant to be used as a wrapper, and put this into the WRAPPER.tt
file:
[% BLOCK form %] [% self.CGI.start_form %] [% self.CGI.hidden(self.config_state_param) %] [% content %] [% self.CGI.submit; self.CGI.end_form %] [% END %]
Now I can define my forms in each page like:
[% WRAPPER form %] ... my fields and text ... [% END %]
Without this hidden state parameter, the dispatcher gets lost in trying to find the page to which this is a response.
You may also want to override some of the other methods, which all
have reasonable defaults. For example, render_enter_per_page
is
called just before a page is rendered. This is a good place to set up
default params (for CGI.pm's sticky fields), or fetch the data for a
``data push'' model (more on this later). By default, nothing happens
in this step.
Another hook provided by CGIPH is the pair of respond_per_app
and
respond_per_page
. The dispatcher first calls respond_per_app
to
handle application-wide buttons or settings. If respond_per_app
returns a true value, the dispatcher treats it as the render
page
object. Otherwise, the dispatcher continues by calling
respond_per_page
, which must return a render object. The
default respond_per_page
returns $self
, meaning that you don't
have to write a method for pages that don't have individualized
responses.
Because self
is passed to the template, a template needing
additional heavy lifting can call back to the render page object
easily. And since these objects (should) inherit from the application
object, the template can also access global methods (or even regional
methods if an additional layer of inheritance is introduced).
For example, to fetch a list of values for display, a page class can define the method to return the value:
sub lotto_picks { my $self = shift; my @data = map { 1 + int rand 100 } 1 .. 10; return \@data; }
and then the template can pick them up:
[% numbers = self.lotto_picks %] Today's lucky numbers are: [% FOR n = numbers %][% n %] [% IF loop.last; ", and "; ELSE; ", "; END %] [% END %]
This callback can start in the page object, but if I discover that I
need it on more than one page, I just move it (cut, paste) into the
My/App.pm
file, and now all pages can access the same callback.
Another means for getting the data to the template is data push. Instead of waiting for the template to ask for the data, compute it once and install it as a slot:
sub render_enter_per_page { my $self = shift; my @data = map { 1 + int rand 100 } 1 .. 10; $self->reflect->addSlot(lotto_picks => \@data); }
Since the page objects inherit from the application object, which
inherits from CGIPH and therefore CGI::Prototype
, which inherits
from Class::Prototyped
, we get ``slot management'' for free. Any new
data value or method can be added at runtime, with the appropriate
getters and setters being installed. And as an added bonus,
Template Toolkit calls this kind of slot to get an array ref in the very
same way as it called the method callback. Yeay.
In a long-running application (such as from mod_perl
, you might
want to free up the data space by removing the slot in
render_leave
. However, for CGI-based applications, the data will
die shortly after the page is shown, so we have no big concern.
Another nice use for created-at-runtime slots is application-wide
notes, such as errors and notices discovered during the processing
of a given web hit. Again, this is quite straightforward. First,
add a slot during app_enter
(defined in My/App.pm
):
sub app_enter { my $self = shift; $self->reflect->addSlot(errors => []); }
and define an easy access method to add a new error:
sub add_errors { push @{shift->errors}, @_ }
Now, any step that notices an error can note it, such as an error
check in respond_enter_per_page
:
unless ($self->param("first") { $self->add_errors("You forgot your first name"); return $self; # stay on this page }
And the template can show the errors, perhaps in the WRAPPER.tt
to ensure uniformity in display:
[% FOR e = self.errors %] [% IF loop.first %] <h2>Errors:</h2> <ul> [% END %] <li> [% e | html %] [% IF loop.last %] </ul> [% END %] [% END %]
So, that's about all there is to the mechanics of CGIPH. I'm slowly
collecting ``best practices'' that I'm becoming aware of while creating
projects for clients, and hope to be gathering that into
CGI::Prototype::Cookbook
, Real Soon Now (hopefully by the time you
see this).
But the one thing that's become obvious to me repeatedly is that the inversion of logic is really helping the design of my web applications. It really does feel like I'm ``calling the user to fill out this form'' instead of the user ``yelling at me to respond to his next hit''. To add a new form and response, I create the view, start with a mostly empty class (just the inheritance and a ``1;''), then flesh out all the heavy lifting things for the form, and then slowly add the plumbing to respnd to the form parameters. The default ``stay here until you get it right'' is a natural user-interface loop design.
So, in summary, consider CGI::Prototype::Hidden
for your next
medium-sized web application. Until next time, enjoy!