NAME

CGI::Framework - A simple-to-use, lightweight web CGI framework

It is primarily a glue between HTML::Template, CGI::Session, CGI, and some magic :)

SYNOPSIS

  use CGI::Framework;
  use vars qw($f);
  
  #
  # Setup the initial framework instance
  #
  $f = new CGI::Framework {
	  sessionsdir		=>	"/tmp",
	  templatesdir		=>	"/home/stuff/myproject/templates",
	  initialtemplate	=>	"enterusername",
  }
  || die "Failed to create a new CGI::Framework instance: $@\n";

  #
  # Get the instance to "do it's magic", including handling the verification of the
  # just-submitting form, preparing the data for the upcoming template to be sent, and any cleanup
  #
  $f->dispatch();

  #
  # This sub is automatically called before the "enterusername" template is sent
  #
  sub validate_enterusername {
	  my $f = shift;
	  if (!$f->form("username")) {
		  $f->adderror("You must enter a username");
	  }
	  elsif (!$f->form("password")) {
		  $f->adderror("You must enter your password");
	  }
	  else {
		  if ($f->form("username") eq "mina" && $f->form("password") eq "verysecret") {
			  $f->session("username", "mina");
			  $f->session("authenticated", "1");
		  }
		  else {
			  $f->adderror("Authentication failed");
		  }
	  }
  }

  #
  # This sub is automatically called before the "mainmenu" template is sent
  #
  sub pre_mainmenu {
	  my $f = shift;
	  $f->assert_session("authenticated");
	  $f->html("somevariable", "somevalue");
	  $f->html("name", $f->session("username"));
  }

  #
  # This sub is automatically called after the "logout" template is sent
  #
  sub post_logout {
	  my $f = shift;
	  $f->clearsession();
  }

DESCRIPTION

CGI::Framework is a simple and lightweight framework for building web-based CGI applications. It features complete code-content separation by utilizing the HTML::Template library, stateful sessions by utilizing the CGI::Session library, form parsing by utilizing the CGI library, (optional) multi-lingual templates support, and an extremely easy to use methodology for the validation, pre-preparation and post-cleanup associated with each template.

CONCEPTUAL OVERVIEW

Before we jump into the technical details, let's skim over the top-level philosophy for a web application:

  • The client sends an initial GET request to the web server

  • The CGI recognizes that this is a new client, creates a new session, sends the session ID to the client in the form of a cookie, followed by sending a pre-defined initial template

  • The user interacts with the template, filling out any form elements, then re-submits the form back to the CGI

  • The CGI reloads the session based on the client cookie, validates the form elements the client is submitting. If any errors are found, the client is re-sent the previous template along with error messages. If no errors were found, the form values are either acted on, or stored into the session instance for later use. The client is then sent the next template.

  • The flow of templates can either be linear, where there's a straight progression from template 1 to template 2 to template 3 (such as a simple ordering form) or can be non-linear, where the template shown will be based on one of many buttons a client clicks on (such as a main-menu always visible in all the templates)

  • Sessions should automatically expire if not loaded in X amount of time to prevent unauthorized use.

IMPLEMENTATION OVERVIEW

Implementing this module usually consists of:

  • Writing the stub code as per the SYNOPSIS. This entails creating a new CGI::Framework instance, then calling the dispatch() method.

  • Creating your templates in the templatesdir supplied earlier. Templates should have the .html extension and can contain any templating variables, loops, and conditions described in the HTML::Template documentation.

  • For each template created, you can optionally write none, some or all of the needed perl subroutines to interact with it. The possible subroutines that, if existed, will be called automatically by the dispatch() method are:

    validate_templatename()

    This sub will be called after a user submits the form from template templatename. In this sub you should use the assert_session() and assert_form() methods to make sure you have a sane environment populated with the variables you're expecting.

    After that, you should inspect the supplied input from the form in that template. If any errors are found, use the adderror() method to record your objections. If no errors are found, you may use the session() method to save the form variables into the session for later utilization.

    pre_templatename()

    This sub will be called right before the template templatename is sent to the browser. It's job is to call the html() method, giving it any dynamic variables that will be interpolated by HTML::Template inside the template content.

    post_templatename()

    This sub will be called right after the template templatename has been sent to the browser and right before the CGI exits. It's job is to do any clean-up necessary after displaying that template. For example, on a final-logout template, this sub could call the clearsession() method to delete any sensitive information.

    If any of the above subroutines are found and called, they will be passed 1 argument, the CGI::Framework instance itself.

STARTING A NEW PROJECT

If you're impatient, skip to the STARTING A NEW PROJECT FOR THE IMPATIENT section below, however it is recommended you at least skim-over this detailed section, especially if you've never used this module before.

The following steps should be taken to start a new project:

SETUP DIRECTORY STRUCTURE

This is the recommended directory structure for a new project:

cgi-bin/

This is where your CGI that use()es CGI::Framework will be placed. CGIs placed there will be very simple, initializing a new CGI::Framework instance and calling the dispatch() method. The CGIs should also add lib/ to their 'use libs' path, then require pre_post and validate.

lib/

This directory will contain 2 important files require()ed by the CGIs, pre_post.pm and validate.pm. pre_post.pm should contain all pre_templatename() and post_templatename() routines, while validate.pm should contain all validate_templatename() routines.

templates/

This directory will contain all the templates you create. Templates should end in the .html extension to be found by the showtemplate() method. More on how you should create the actual templates in the CREATE TEMPLATES section

sessions/

This directory will be a temporary holder for all the session files. It's permissions should allow the user that the web server runs as (typically "nobody") to write to it.

public_html/

This directory should contain any static files that your templates reference, such as images, style sheets, static html links, multimedia content, etc...

CONFIGURE YOUR WEB SERVER

How to do this is beyond this document due to the different web servers out there, but in summary, you want to create a new virtual host, alias the document root to the above public_html/ directory, alias /cgi-bin/ to the above cgi-bin/ directory and make sure the server will execute instead of serve files there, and in theory you're done.

CREATE TEMPLATES

You will need to create a template for each step you want your user to see. Templates are regular HTML pages with the following additions:

CGI::Framework required tags

The CGI::Framework absolutely requires you insert these tags into the templates. No ands, iffs or butts about it. The framework will NOT work if you do not place these tags in your template:

<cgi_framework_header>

Place this tag right under the <body> tag

<TMPL_INCLUDE NAME="errors.html">

Place this tag wherever you want errors added with the adderror() method to appear

<cgi_framework_footer>

Place this tag right before the </body> tag

It is recommended that you utilize HTML::Template's powerful <TMPL_INCLUDE> tag to create base templates that are included at the top and bottom of every template (similar to Server-Side Includes, SSIs). This has the benefit of allowing you to change the layout of your entire site by modifying only 2 files, as well as allows you to insert the above 3 required tags into the shared header and footer templates instead of having to put them inside every content template.

HTML::Template tags

All tags mentioned in the documentation of the HTML::Template module may be used in the templates. This allows dynamic variable substitutions, conditionals, loops, and a lot more.

To use a variable in the template (IFs, LOOPs, etc..) , it must either:

  • Have just been added using the html() method, probably in the pre_templatename() routine.

  • Has just been submitted from the previous template

  • Has been added in the past to the session using the session() method.

  • Has been added automatically for you by CGI::Framework. See the DEFAULT TEMPLATE VARIABLES section.

CGI::Framework language tags

If you supplied a "validlanguages" arrayref to the new() constructor of CGI::Framework, you can use any of the languages in that arrayref as simple HTML tags. This allows you to easily write multi-lingual templates, simply by surrounding each language with the appropriate tag. Depending on the client's chosen language, all other languages will not be served.

For example, if your new() constructor included:

validlanguages	=>	['en', 'fr']

You can then use in the template something like this:

<en>Good morning</en>
<fr>Bonjour</fr>

And the user will be served the right one.

"The right one" needs some elaboration here: By default, the first language supplied in the validlanguages arrayref will be set as the default language. The user could then change their default language at any point by submitting a form element named "_lang" and a value set to any of the values in the arrayref.

The process() javascript function

This javascript function will become available to all your templates and will be sent to the client along with the templates. Your templates should call this function whenever the user has clicked on something that indicates they'd like to move on to the next template. For example, if your templates offer a main menu at the top with 7 options, each one of these options should cause a call to this process() javascript function. Every next, previous, logout, etc.. button should cause a call to this function.

This javascript function accepts the following parameters:

templatename

MANDATORY

This first parameter is the name of the template to show. For example, if the user clicked on an option called "show my account into" that should load the accountinfo.html template, the javascript code could look like this:

<a href="#" onclick="process('accountinfo');">Show my account info</a>
item

OPTIONAL

If this second parameter is supplied to the process() call, it's value will be available back in your perl code as key "_item" through the form() method.

This is typically used to distinguish between similar choices. For example, if you're building a GUI that allows the user to change the password of any of their accounts, you could have something similar to this:

bob@domain.com   <input type="button" value="CHANGE PASSWORD" onclick="process('changepassword', 'bob@domain.com');">
<br>
mary@domain.com  <input type="button" value="CHANGE PASSWORD" onclick="process('changepassword', 'mary@domain.com');">
<br>
john@domain.com  <input type="button" value="CHANGE PASSWORD" onclick="process('changepassword', 'john@domain.com');">
skipvalidation

OPTIONAL

If this third parameter is supplied to the process() call with a true value such as '1', it will cause CGI::Framework to send the requested template without first calling validate_templatename() on the previous template and forcing the correction of errors.

The errors template

It is mandatory to create a special template named errors.html. This template will be included in all the served pages, and it's job is to re-iterate over all the errors added with the adderror() method and display them. A simple errors.html template looks like this:

errors.html sample:
<TMPL_IF NAME="_errors">
	<font color=red><b>The following ERRORS have occurred:</b></font>
	<blockquote>
		<TMPL_LOOP NAME="_errors">
			* <TMPL_VAR NAME="error"><br>
		</TMPL_LOOP>
	</blockquote>
	<font color=red>Please correct below and try again.</font>
	<p>
</TMPL_IF>
The missing info template

It is recommended, although not mandatory, to create a special template named missinginfo.html. This template will be shown to the client when an assertion made through the assert_form() or assert_session() methods fail. It's job is to explain to the client that they're probably using a timed-out session, and invites them to start from the beginning.

If this template is not found, the above error will be displayed to the client in a text mode.

ASSOCIATE THE CODE WITH THE TEMPLATES

For each template you created, you might need to write a pre_templatename() sub, a post_templatename() sub and a validate_templatename() sub as described earlier. None of these subs are mandatory.

For clarity and consistency purposes, the pre_templatename() and post_templatename() subs should go into the pre_post.pm file, and the validate_templatename() subs should go into the validate.pm file.

WRITE YOUR CGI

Copying the SYNOPSIS into a new CGI file in the cgi-bin/ directory is usually all that's needed unless you have some advanced requirements such as making sure the user is authenticated first before allowing them access to certain templates.

TEST, FINE TUNE, ETC . . .

Every developer does this part, right :) ?

STARTING A NEW PROJECT FOR THE IMPATIENT

  • Install this module

  • Run: perl -MCGI::Framework -e 'CGI::Framework::INITIALIZENEWPROJECT "/path/to/your/project/base"'

  • cd /path/to/your/project/base

    Customize the stubs that were created there for you. Refer back to the not-so-impatient section above for clarifications of anything you see there.

OBJECT-ORIENTED VS. FUNCTION MODES

This module allows you to use an object-oriented or a function-based approach when using it. The only drawback to using the function-based mode is that there's a tiny bit of overhead during startup, and that you can only have one instance of the object active within the interpreter (which is not really a logical problem since that is never a desirable thing. It's strictly a technical limitation).

THE OBJECT-ORIENTED WAY

As the examples show, this is the object-way of doing things:

use CGI::Framework;

my $instance = new CGI::Framework (
	this	=>	that,
	foo	=>	bar,
);

$instance->dispatch();

sub validate_templatename {
	my $instance = shift;
	if (!$instance->form("countries")) {
		$instance->adderror("You must select a country");
	}
	else {
		$instance->session("country", $instance->form("countries"));
	}
}

sub pre_templatename {
	my $instance = shift;
	$instance->html("countries", [qw(CA US BR)]);
}
THE FUNCTION-BASED WAY

The function-based way is very similar (and slightly less cumbersome to use due to less typing) than the OO way. The differences are: You have to use the ":nooop" tag in the use() line to signify that you want the methods exported to your namespace, as well as use the initialize_cgi_framework() method to initialize the instance instead of the new() method in OO mode. An example of the function-based way of doing things:

use CGI::Framework ':nooop';

initialize_cgi_framework (
	this	=>	that,
	foo	=>	bar,
);

dispatch();

sub validate_templatename {
	if (!form("countries")) {
		adderror("You must select a country");
	}
	else {
		session("country", form("countries"));
	}
}

sub pre_templatename {
	html("countries", [qw(CA US BR)]);
}

THE CONSTRUCTOR / INITIALIZER

new(%hash)

This is the standard object-oriented constructor. When called, will return a new CGI::Framework instance. It accepts a hash with the following keys:

callbacksnamespace

OPTIONAL

This key should have a scalar value with the name of the namespace that you will put all the validate_templatename(), pre_templatename() and post_templatename() subroutines in. If not supplied, it will default to the caller's namespace. Finally if the caller's namespace cannot be determined, it will default to "main".

The main use of this option is to allow you, if you so choose, to place your callbacks subs into any arbitrary namespace you decide on (to avoid pollution of your main namespace for example).

cookiename

OPTIONAL

This key should have a scalar value with the name of the cookie to use when communicating the session ID to the client. If not supplied, will default to "sessionid_" and a simplified representation of the URL.

initialtemplate

MANDATORY

This key should have a scalar value with the name of the first template that will be shown to the client when the dispatch() method is called.

importform

OPTIONAL

This variable should have a scalar value with the name of a namespace in it. It imports all the values of the just-submitted form into the specified namespace. For example:

importform	=>	"FORM",

You can then use form elements like:

$error = "Sorry $FORM::firstname, you may not $FORM::action at this time.";

It provides a more flexible alternative to using the form() method since it can be interpolated inside double-quoted strings, however costs more memory. I am also unsure about how such a namespace would be handled under mod_perl and if it'll remain persistent or not, possibly causing problems.

sessionsdir

OPTIONAL

This key should have a scalar value holding a directory name where the session files will be stored. If not supplied, a suitable temporary directory will be picked from the system.

templatesdir

OPTIONAL

This key should have a scalar value holding a directory name which contains all the template files. If not supplied, it will be guessed based on the local directory.

validlanguages

OPTIONAL

This key should have an arrayref value. The array should contain all the possible language tags you've used in the templates.

initialize_cgi_framework(%hash)

Just like the above new() constructor, except used in the function-based approach instead of the object-oriented approach.

METHODS / FUNCTIONS

adderror($scalar)

This method accepts a scalar error and adds it to the list of errors that will be shown to the client. It should only be called from a validate_templatename() subroutine for each error found during validating the form. This will cause the dispatch() method to re-display the previous template along with all the errors added.

assert_form(@array)

This method accepts an array of scalar values. Each element will be checked to make sure that it has been submitted in the just-submitted form and has a true value. If any elements aren't found or have a false value, the missinginfo template is shown to the client.

assert_session(@array)

Just like the assert_form() method, except it checks the values against the session instead of the submitted form.

clearsession

This method deletes all the previously-stored values using the session() method.

dispatch

This method is the central dispatcher. It calls validate_templatename on the just-submitted template, checks to see if any errors were added with the adderror() method. If any errors were added, re-sends the client the previous template, otherwise sends the client the template they requested.

form($scalar)

This method accepts an optional scalar as it's first argument, and returns the value associated with that key from the just-submitted form from the client. If no scalar is supplied, returns all entries from the just-submitted form.

get_cgi_object

Returns the underlying CGI object. To be used if you'd like to do anything fancy this module doesn't provide methods for, such as processing extra cookies, etc...

get_cgi_session_object

Returns the underlying CGI::Session object. To be used if you'd like to do anything fancy this module doesn't provide methods for.

html($scalar, $scalar)

This method accepts a scalar key as it's first argument and a scalar value as it's second. It associates the key with the value in the upcoming template. This method is typically called inside a pre_template() subroutine to prepare some dynamic variables/loops/etc in the templatename template.

html_push($scalar, $scalar)

Very similar to the above html() method, except it treats the key's value as an arrayref (creates it as an arrayref if it didn't exist), and push()es the value into that array. This method is typically used to append to a key that will be used in a template loop with HTML::Template's <TMPL_LOOP> tag, the value in which case is normally a hashref.

html_unshift($scalar, $scalar)

Very similar to the above html_push() method, except it unshift()s instead of push()es the value.

session($scalar [, $scalar])

This method accepts a scalar key as it's first argument and an optional scalar value as it's second. If a value is supplied, it saves the key+value pair into the session for future retrieval. If no value is supplied, it returns the previously-saved value associated with the given key.

showtemplate($scalar)

This method accepts a scalar template name, calls the pre_templatename() sub if found, sends the template to the client, calls the post_templatename() sub if found, then exists. While sending the template to the client it also takes care of the <cgi_framework_header>, <cgi_framework_footer> tags, as well as the language substitutions.

DEFAULT TEMPLATE VARIABLES

Aside from variables added through the html() method, the submitted form and the current session, these pre-defined variables will be automatically set for you to use in your templates:

_formaction

This variable will contain the URL to the current CGI

BUGS

I do not (knowingly) release buggy software. If this is the latest release, it's probably bug-free as far as I know. If you do find a problem, please contact me and let me know.

AUTHOR

Mina Naguib
CPAN ID: MNAGUIB
mnaguib@cpan.org
http://www.topfx.com

COPYRIGHT

This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.

The full text of the license can be found in the LICENSE file included with this module.

Copyright (C) 2003 Mina Naguib.

SEE ALSO

HTML::Template, CGI::Session, CGI.