use Framework; use CGI; my $coll = new Framework::Collection; $coll->templater(new TemplatingEngine); my $p1 = new Framework::Page("login", -description => 'Login page'); $p1->validator(sub { my $page = shift; my $pobj = $page->paramobj; return 1 if ($pobj->param('name') eq 'bmcatt'); $page->add_error('name', 'invalid name given'); return 0; } ); $p1->traverse(sub { my $page = shift; return 'login' if ($page->errors(':any')); return 'main'; } $p1->template('login.tmpl'); my $p2 = new Framework::Page("main", -description => 'Main Page' -hparams => [ 'name' ]); $p2->output( sub { my ($page, $paramref, $output) = @_; my $s = "Content-type: text/html\n\n"; $s .= "
You have logged in as " . $paramref->{'name'} . ""; return $s; } $coll->page($p1); $coll->page($p2); $coll->defpage($p1); $coll->run(new CGI); print $coll->output; ####
#! perl -w
package Framework;
# XXX Add small working example to POD
=pod
=head1 NAME
Framework - Perl module to provide command and control functionality
over templatized web pages
=head1 SYNOPSIS
use CGI;
use Framework;
$p = new Framework::Page("name", -description => "Initial page",
-template => "name.tmpl");
$p->traverse(\&traverse_name);
$c = new Framework::Collection;
$c->page($p);
$c->templater($templating_engine_instance);
$c->run(new CGI);
print $c->output;
=head1 DESCRIPTION
Framework provides a way to maintain, display and arbitrate the
selection of templatized web pages. It does this through the paradigm
of individual Is which are bundled together into a
I.
The Collection controls selection of which Page is to be executed
(based on parameter analysis). The selected Page controls parameter
validation and selection of the next Page to be processed. Once a
target page has been determiend, that Page is processed for its
template and is run through the templating engine.
=cut
use strict;
use vars qw($VERSION);
use constant DEBUG => 1;
$VERSION = '0.02';
my $DEFAULT_ERROR_SEP = '|';
package Framework::Collection;
=pod
=head2 Framework::Collection
=over 4
=cut
sub debugprint {
my $level = shift;
print @_ if (Framework::DEBUG >= $level);
}
my $output;
=pod
=item new()
Create a new Collection. Usually called as:
my $coll = new Framework::Collection();
=cut
sub new {
my $this = shift; my $class = ref($this) || $this;
my $self = {
_PAGES => {},
_DEFAULT_PAGE => undef,
};
bless $self, $class;
}
=pod
=item page($page)
=item page($name)
When called with a Framework::Page, adds C<$page> to the list of pages
to be considered for processing.
When called with a string C<$name>, returns the Framework::Page with
the name C<$name> or C.
The first C<$page> added to a Collection is the default page unless
overriden through a call to C.
=cut
sub page {
my $self = shift;
my ($page) = @_;
if ($page->isa("Framework::Page")) {
# Add to _PAGES{}
$self->{_PAGES}->{$page->{_NAME}} = $page;
$self->defpage($page) unless ($self->defpage());
return $page; # Just to have something useful returned
} else {
# $page is a page *NAME*, not an actual page...
# Return _PAGES{$page};
if (exists $self->{_PAGES}->{$page}) {
return $self->{_PAGES}->{$page};
}
return undef;
}
}
=pod
=item defpage($page)
=item defpage($name)
Sets the default page to be processed, either by name or by
Framework::Page object. The default page is the page of I
and is used if the Collection cannot determine the calling page. This
is typically used during the first invocation of the application.
=cut
sub defpage {
my $self = shift;
my ($page) = @_;
if (!$page) { return $self->{_DEFAULT_PAGE}; }
$self->{_DEFAULT_PAGE} = ( $page->isa('Framework::Page')
? $page->{_NAME}
: $page );
}
=pod
=item templater(I<$templating_instance>)
Assigns / returns the instance of the templating object which is to be
used for processing the template of the destination page. If called
without arguments, the current value is returned. The
C<$templating_instance> will eventually be called as:
&$templating_instance($template, \%params);
=cut
sub templater {
my $self = shift;
if (@_) { $self->{_TEMPLATER} = shift; }
return $self->{_TEMPLATER};
}
# We need this here because both run and output() need to have access
# to it...
my ($targpage_id, $targpage);
=pod
=item run($paramobj)
Uses C<$paramobj> (usually an instance of a CGI object) to process the
Collection and determine the correct target Page. At a minimum,
C<$paramobj> must support a scalar and list context param() for
retrieval and assignment and a hidden() method. The processing flow
during a run is:
=over 4
=item 1
Determine the current Page name
=item 2
Invoke current page's validator subref (if defined)
=item 3
If no validator or the validator returned C, invoke current
page's postsubmit subref (if defined)
=item 4
Invoke current page's traverse subref (if defined). The traverse
subref is expected to return the name of the target page. If there is
no traverse subref or it returns C, the current page is used as
the target page.
=item 5
Invoke target page's preprocess subref (if defined).
=back
The return value from the target page's preprocess subref will be used
as the return value from C.
=cut
sub run {
my $self = shift;
my ($pobj) = @_;
# Determine originating page
my $curpage_id = $pobj->param("_PAGE_CURRENT")
|| $self->{_DEFAULT_PAGE};
debugprint 1, "Starting at page $curpage_id\n";
# Get Framework::Page for current page
my $curpage = $self->page($curpage_id);
# Clear out any old cruft
$curpage->_phase(':runconfig');
$curpage->paramobj($pobj);
# Run $curpage->validator->()
$curpage->_phase(':valid');
my $val_return = 1;
if ($curpage->validator) {
$val_return = &{$curpage->validator()}($curpage);
}
# Run $curpage->postsub->() if validator->() returned TRUE
$curpage->_phase(':postsub');
if ($val_return && $curpage->postsubmit) {
&{$curpage->postsubmit()}($curpage);
}
# Run $curpage->traverse->()
$curpage->_phase(':traverse');
$targpage_id = $curpage_id;
if ($curpage->traverse) {
$targpage_id = &{$curpage->traverse()}($curpage);
if (!$targpage_id) { $targpage_id = $curpage_id; }
}
# Save error list and retract parameter object from old page
my $errlist = $curpage->errors(':all');
$curpage->_phase(':undef');
# Get Framework::Page for target page
debugprint 1, "Transitioning to $targpage_id\n";
$targpage = $self->page($targpage_id);
$targpage->paramobj($pobj);
# Transfer error set from original to target
$targpage->_phase(':seterrors', $errlist);
# Clear out the output
$output = '';
# Run $targpage->preprocess->()
$targpage->_phase(':preproc');
if ($targpage->preprocess) {
$output = &{$targpage->preprocess()}($targpage);
}
# Return output from preprocess->()
return $output;
}
sub _get_params {
my $p = shift;
my $pobj = $p->paramobj;
my %paramhash;
if ($p->aparams) {
foreach ($p->aparams) {
my @param = $pobj->param($_);
my $pval = ( @param == 1 ? $param[0] : \@param );
$paramhash{$_} = $pval;
}
}
my $hidden;
if ($p->hparams) {
foreach ($p->hparams) {
my @param = $pobj->param($_);
$hidden .= $pobj->hidden($_, @param);
}
}
$hidden .= $pobj->hidden('_PAGE_CURRENT', $p->{_NAME});
$paramhash{_PAGE_HIDDEN} = $hidden;
$paramhash{_PAGE_DESC} = $p->{_DESC};
my $errs = $p->errors;
foreach (keys %$errs) {
$paramhash{'_ERROR_' . $_} = $errs->{$_};
}
# XXX Add other parameters (errors, etc).
return \%paramhash;
}
=pod
=item output()
Invokes the templating engine on the template for the target page.
Also invokes the target page's output subref (if defined). The
templating engine is only invoked if both it and the target page's
template are defined.
The return value is the output of the templating engine, possibly
after it has been subsequently passed through the target page's output
subref.
=cut
sub output {
my $self = shift;
my $params = _get_params($targpage);
if ($targpage->template && $self->{_TEMPLATER}) {
$output = $self->templater->($targpage->template, $params);
}
if ($targpage->output) {
$output = $targpage->output->($targpage, $params, $output);
}
# Retract parameter object from targpage
$targpage->_phase(':undef');
return $output;
}
=pod
=back
=cut
package Framework::Page;
=pod
=head2 Framework::Page
=over 4
=cut
sub debugprint {
my $level = shift;
print @_ if (Framework::DEBUG >= $level);
}
# Externally visible methods that are intended to be called by those wanting
# to create/modify a page.
my %PAGE_KEY_NAMES = (
-description => '_DESC',
-validator => '_VALID',
-postsubmit => '_POST',
-traverse => '_TRAV',
-preprocess => '_PREPROC',
-template => '_TMPL',
-output => '_OUTPUT',
-hparams => '_HPARAMS',
-aparams => '_APARAMS',
-error_sep => '_ERROR_SEP',
);
my @ERROR_CLASSES = qw(:valid :postsub :traverse :preproc);
my %PAGE_CTOR_DEFAULTS = (
_DESC => "",
_VALID => undef,
_POST => undef,
_TRAV => undef,
_PREPROC => undef,
_TMPL => undef,
_OUTPUT => undef,
_HPARAMS => [],
_APARAMS => [],
_ERROR_SEP => $DEFAULT_ERROR_SEP,
_PHASE => ':valid',
);
sub _check_params {
my %params = @_;
my %outparams;
foreach my $pname (keys %params) {
# XXX Better error handling - bad parameter specified.
die "A horrible death" if (!exists($PAGE_KEY_NAMES{$pname}));
$outparams{$PAGE_KEY_NAMES{$pname}} = $params{$pname};
}
return %outparams;
}
=pod
=item new($name, ...)
=item $page->new($newname, ...)
Creates a new Page instance using C<$name> as the page's name. When
called as a method on an existing Page, the new Page will be
configured exactly the same as the existing page, except for having a
new name.
Optional parameters may be included to "short-circuit" the process of
creating a new Page. An example of creating a Page with all
short-circuit parameters used is:
$p = new Framework::Page('TestPage',
-description => "This is a test page",
-validator => \&valid_sub,
-postsubmit => \&postsubmit_sub,
-traverse => \&traverse_sub,
-preprocess => \&preprocess_sub,
-template => "TestPage.tmpl",
-output => \&output_sub,
-hparams => [ 'hidden1', 'hidden2' ],
-aparams => [ 'avail1', 'avail2' ],
-error_sep => '
'
);
=cut
sub new {
my $this = shift; my $class = ref($this) || $this;
my $name = shift;
# XXX Better error handling - force a name to be given.
die "You silly rabbit" if (!defined($name));
my %params = _check_params(@_);
if (ref($this) && $this->isa('Framework::Page')) {
# called as a copy constructor...
my $self = {
%$this,
%params,
};
bless $self, $class;
$self->_phase(':runconfig');
return $self;
}
my $self = {
_NAME => $name,
%PAGE_CTOR_DEFAULTS,
%params,
};
bless $self, $class;
$self->_phase(':runconfig');
return $self
}
=pod
=item description($desc)
Assigns / retrieves a textual description for this Page. This is not
used internally, but is passed to the templating engine as the
parameter C<_PAGE_DESC>.
=cut
sub description ($$) {
my $self = shift;
if (@_) { $self->{_DESC} = shift; }
return $self->{_DESC};
}
=pod
=item validator(\&validator)
Assigns / retrieves the subref for the validation routine to be used
to determine whether the parameters for this page were valid. Called
as:
&validator($curpage);
If C<&validator> returns C, it indicates that the subsequent
postsubmit subref is to be called. Otherwise, the postsubmit is
skipped and the traverse subref is called.
B: It was the authors' intention that &validator not mutate any
of the back-end data or system state. Any changes to the system state
were intended to be done in the postsubmit routine following
successful validation.
=cut
sub validator ($\&) {
my $self = shift;
if (@_) { $self->{_VALID} = shift; }
return $self->{_VALID};
}
=pod
=item postsubmit(\&postsubmit)
Assigns / retrieves the subref for the postsubmit routine to be used
to perform post-validation processing. Called as:
&postsubmit($curpage);
The return value from C<&postsubmit> is ignored by the Collection.
=cut
sub postsubmit ($\&) {
my $self = shift;
if (@_) { $self->{_POST} = shift; }
return $self->{_POST};
}
=pod
=item traverse(\&traverse)
Assigns / retrieves the subref for the traversal routine to be used to
determine what the appropriate target page is. Called as:
&traverse($curpage);
The return value from C<&traverse> should be the name of the target
page to be processed or C if the current page should be used as
the target page.
=cut
sub traverse ($\&) {
my $self = shift;
if (@_) { $self->{_TRAV} = shift; }
return $self->{_TRAV};
}
=pod
=item preprocess(\&preprocess)
Assigns / retrieves the subref for the preprocessing routine to be
used as the last stage of a Collection's C. Called as:
&preprocess($targetpage);
The return value from C<&preprocess> is used as the return value from
Collection's C.
=cut
sub preprocess ($\&) {
my $self = shift;
if (@_) { $self->{_PREPROC} = shift; }
return $self->{_PREPROC};
}
=pod
=item template($template_filename)
Assigns / retrieves the name of the file to be passed to the
templating engine when the Collection's C