dcsimg

CGI::Prototype, Part Three

In the last two columns, I introduced my CGI::Prototype generic controller framework. This time, let’s continue the examination with a description of a real workhorse subclass, CGI::Prototype::Hidden.
In the last two columns, I introduced my CGI::Prototype generic controller framework. This time, let’s continue the examination with a description of 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 to be coded is hidden fields, implemented as CGI::Prototype::Hidden.
For CGI::Prototype::Hidden (CGIPH), 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’s 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 state is 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, as in:
package My::App;
use base CGI::Prototype::Hidden;

sub config_state_param { "--state--" }
Now, the state parameter is in –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 maps to the class 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 goes 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 runs the corresponding individual page template. However, you’ll 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 the 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 %]
… you 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 the Template Toolkit’s search path. If you want a different policy, feel free to override engine_config from the value given in the CGIPH man page with your own INCLUDE_PATH definition.
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 state passing is to create a Template Toolkit 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, you can define your 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 parameters (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 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 you discover that you need it on more than one page, just move it (cut, paste) into the My/App.pm file. 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. Yay!
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 read 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;), 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!

Randal Schwartz is the chief Perl guru at Stonehenge Consulting. You can reach Randal at class="emailaddress">merlyn@stonehenge.com.

Comments are closed.