Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl: the Markov chain saw
 
PerlMonks  

Framework.pm 0.01a

by boo_radley (Parson)
on May 05, 2001 at 06:30 UTC ( [id://78183]=CUFP: print w/replies, xml ) Need Help??

Framework is a way to automate the upkeep and processing of CGI scripts through a well defined structure which implements pre- and post- CGI submission handling, business rule validation and templatization.

This is the project which I've commented on several times here, and I wish to submit it to brutal peer review.

First off, a revisit to the structure that drives the module. The primary datastore is %pages, which contains one or more page items, identified by their pagename. each page item contains several keys, as follows.
module : Optional, but highly recommended. A page's perl code can be split out into several modules, and whatever's in the file specified by the module key will be pushed into the MAIN namespace. description : A string of questionable value traverse_list : hash where the keys are pagenames, and the values are true/ false to indicate if post-submit processing is needed when traversing to this page. traverse_sub : reference to a sub used to determine which member of traverse list to access. validator : sub reference used to validate data on the current page. errorhandler : Not currently implemented. template : String containing filepath to a TT2 template Contains the HTML that will actually be displayed. preprocess : Sub reference run before the page is displayed. postsubmit : Sub reference run after the page is displayed. params : Determines which CGI params are to be kept alive between invocations.
OK.
Here is a small snippet from my proof of concept program (a small web journal) showing how to add pages to the collection, and then run the collection :
#!e:\perl\bin\perl.exe use strict; use Framework; use CGI; use DBI; use Date::Manip; use constant BASEPATH => 'c:/progra~1/apache~1/apache/cgi-bin/'; InsertPage_hashref ( { pagename=> "Entry", module => BASEPATH . 'entry.pl', description => "Entrance to the journal", traverse_list => {Entry=>0, writejournal => 0, readjournal => 0, editjournal => 0}, traverse_sub => \&entry_trav, validator => undef, errorhandler => undef, template => 'templates/JEntry.html', preprocess => undef, postsubmit => undef, params => ["read.x","write.x","edit.x"]}); InsertPage_hashref ( { pagename=> "writejournal", module => BASEPATH . 'jwrite.pl', description => "write a new journal entry", traverse_list => {Entry=>0, writejournal => 1, readjournal => 0}, traverse_sub => \&entry_trav, validator => \&write_valid, errorhandler => undef, template => 'templates/Jwrite.html', preprocess => undef, postsubmit => \&write_sub, params => []}); RunPages;
The first two statements add two HTML pages and the subroutines and arrays required to automate the page's upkeep.
RunPages process the CGI parameters and runs the current page's subroutines to verify and process data.

Following is a sample of some of the subs that run behind the scenes. First up is a sub called write_sub, which takes the entry the user's working on, and saves it to a databse. Firstly, the Framework variables and errorcode are passed in. The get_errorlevel call performs the same function as shifting in $errorcode; this demonstrates 2 methods of doing the same thing, although I think the shiftiness is going to go away.
After checking to make sure all the data is valid -- e.g. that $errorcode is false, it prepares to insert the journal entry into a csv database. most of the code for dealing with the CSV, replicating an IDENTITY row...
sub write_sub { my $vars = shift; my $errorcode = shift; if (&get_errorlevel) {return} # don't write anything to the +database if something's errored out on us. my $dbh = DBI->connect("DBI:CSV:f_dir=c:/journal/"); my $sth = $dbh->prepare ("SELECT id FROM entries") || die "$! and +$DBI::errstr"; $sth->execute; my $maxid; while (my $thisrow = $sth->fetchrow_arrayref) { if ($$thisrow[0] > $maxid) {$maxid = $$thisrow[0]} }; $maxid++; my $title = $dbh->quote ($$$vars{params}{title}); my $date = $dbh->quote (scalar localtime); my $entry = $dbh->quote (unpack ("H*",$$$vars{params}{entry})); + # Hi, Tye! $sth = $dbh->prepare ("INSERT INTO entries (id, date, title, entry +) VALUES ($maxid, $date,$title, $entry)")|| die "$! and $DBI::errstr" +; $sth->execute; }
More or less straightforward, I hope. But where does the errorcode come from, and what it tell us?
Before we get to the point of post submittal processing, the following sub is called to make sure the data on this HTML page meets whatever critera I choose. In this case, I want to make sure there's a title and and an entry. If there isn't, I set two flags in the sub's return code, $ret_code. This return value is then passed to write_sub.
sub write_valid { my $vars = shift; my $error = shift; my $ret_code; my @t = param ("title"); my @e = param ("entry"); unless ($t[-1]){$ret_code |= 1}; unless ($e[-1]){$ret_code |= 2}; return $ret_code; }
This shows one particular segment of how the framework is used. It segments common tasks into automatable subroutines, and allows the programmer to decide how to build upon the framework.
Here is the code for Framework.PM.
package Framework; #!/usr/bin/perl -w # In nomine Ad Signo, et Percentus, et Dollar Signo Sancti. Amen. use strict; require Exporter; use Template; use DBI; use CGI::Carp qw(fatalsToBrowser); use CGI qw(:all); use constant DEBUG => 0; use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS); use vars qw($defaultpage $Current_Page %pages); my $vars; my $errorlevel; @ISA = qw(Exporter); @EXPORT = qw(%pages &InsertPage_hashref &InsertPage_arrayref &Run +Pages $defaultpage &dump_struct &set_errorlevel &get_errorlevel &set_var &get_var); # # inserts a page into the hash via an array. # # sub InsertPage_arrayref ($) { my @_parms=@{@_[0]}; # Make sure there's the right # of keys to do the job. if (scalar @_parms == 11) { unless ($defaultpage) {$defaultpage = $_parms[0]}; if (defined $_parms[1]){ load_lib ($_parms[1],"main")}; $pages {$_parms[0]} = { description => $_parms[1], traverse_list => $_parms[3], traverse_sub => $_parms[4], validator => $_parms[5], errorhandler => $_parms[6], template => $_parms[7], preprocess => $_parms[8], postsubmit => $_parms[9], params => $_parms[10], } } else { die "wrong number of elements passed to InsertPage_arrayref. E +xpected 11, got ". (scalar @_parms) } } # # inserts a page into the %pages hash via a hash. # # sub InsertPage_hashref ($) { my %_parms= %{@_[0]}; # Make sure there's the right keys to do the job. if (CheckKeys (\%_parms,["pagename","description","traverse_list", "traverse_sub","validator","errorhandler", "template","preprocess","postsubmit","params" +])) { # # ok, all the parameters are there, what about the default p +age? # # unless ($defaultpage) {$defaultpage = $_parms{pagename}}; # # check to see if we should load a module for this page. # # if (defined $_parms{module}){ load_lib ($_parms{module},"main" +)}; #eval "require \"$_parms{module}\""|| die "no module"; $pages {$_parms{pagename}} = { description => $_parms{description }, traverse_list => $_parms{traverse_list}, traverse_sub => $_parms{traverse_sub }, validator => $_parms{validator }, errorhandler => $_parms{errorhandler }, template => $_parms{template }, preprocess => $_parms{preprocess }, postsubmit => $_parms{postsubmit }, params => $_parms{params }, } } } # # Checks a hash to make sure a set of keys exist. # # sub CheckKeys (\%\@) { my %hash = %{$_[0]}; my @keys = @{$_[1]}; foreach (@keys) { if (! exists ($hash{$_})) { return 0} } return 1; } # # This sub is the meat of this module... # it uses information to display HTML pages from # %pages. Verifies parameters, and contains logic to # perform pre and post display functions. # # sub RunPages { print header (-type=>'text/html'); print start_html; print start_form; # remember that all hidden fields need to be w +ithinthe form, they're input! debugprint ( "checking Current_page ".br); # # separate into sub # # # # Current_Page is a parameter that will be set immediately befor +e a page is displayed. # Inside of the program, it is analogous to $Current_Page, and i +s used to determine # what set of subroutines should be run. # If param ("Current_Page") exists, that means that the user's C +GI script has run through at least once. # We'll grab it's contents and stuff it into $Current_Page. # But if param ("Current_Page") doesn't exist, then we set $Curr +ent_Page to the # contents of $defaultpage, which is typically the first page in +serted. # Note that $defaultpage is exported, so that users can set it t +o whichever page they prefer. # to display as their first. # if (param ("Current_Page")){ debugprint ("found Current_page set to ".param ("Current_Page" +).br); $Current_Page = param ("Current_Page"); debugprint ("Checking Current_Page for validator<BR>"); # # Here, we are checking to see if the current page has a validat +or defined. # If it does, run it and store the result in $errorlevel. # $errorlevel is then passed to all other subs as a way of deter +mining state. # if (defined ($pages{$Current_Page}{validator})) { $errorlevel =$pages{$Current_Page}{validator}->(); debugprint ("validator found, returned '$errorlevel'.<BR>" +); } } else { debugprint( "setting Current_Page to default page $defaultpage +<BR>"); $Current_Page = $defaultpage; } debugprint ("printing parameters<BR>"); # # Here we go through each page, and see what its params are. # Note that I rarely use param lists, so I only write each # parameter's last value. # This prevents duplication of parameters when pages refer back # to themselves. # I consider this to be a problem, but I'm not sure how to fix i +t :| # foreach my $thispage (keys %pages){ foreach (@{$pages{$thispage}{params}}) { debugprint ("looking at param $_"); my @temp_param = param($_); if ((param($_))){print hidden ($_, $temp_param[-1])} $$vars{params}{$_}= $temp_param[-1]; debugprint ("Parameter '$_' set to '$temp_param[-1]'<B +R>"); } } debugprint ("Done printing parameters<BR>"); debugprint ("Checking traversal list<BR>"); my $nextpage; # # Here we check the current page's traversal list. # As a default, if it has no traverse_sub, it'll loop back to $C +urrent_Page. # Right now, the traverse_sub is called with references to $vars + and $errorlevel, but # this should be unneeded with the introduction of (get|set)_(va +r|errorlevel|param) # I'm leaving it in right now. # # As a side effect of the order of processing things, it becomes + convenient to # check for param("Current_Page") in the default page's validato +r, and have it return itself # if param("Current_Page") doesn't exist. # if ($pages{$Current_Page}{traverse_sub} ne "") { debugprint ("Traversal list found... <BR>"); $nextpage = $pages{$Current_Page}{traverse_sub}->(\$vars,\$err +orlevel); } else { debugprint ("No traverse_sub for page 'Current_Page'. Are you +sure about that? Setting next_page to 'Current_Page'"); $nextpage = $Current_Page; } debugprint ("Traverse_sub returns '$nextpage'<BR>"); # # Paranoia? # if (defined $nextpage) { # # make sure that $nextpage is in the current page's traverse + list. # unless (defined ($pages{$Current_Page}{traverse_list}{$nextpag +e})){ debugprint ("tried to access undefined key in $Current_Pag +e traverse_list : $nextpage"); #die; } # # SO we survived that. # Now we check the value of $nextpage 's entry in traverse_l +ist. # If it's true, we check for and execute $Current_Page's pos +tsubmit. # debugprint ("Checking for '$Current_Page' traverse for post su +bmit <BR>"); if ($pages{$Current_Page}{traverse_list}{$nextpage}){ if (defined $pages{$Current_Page}{postsubmit}) { debugprint ("Executing '$Current_Page' post submit<BR> +"); $pages{$Current_Page}{postsubmit}->(\$vars, \$errorlev +el); debugprint ("Done with '$Current_Page' post submit<BR> +"); } else { debugprint ("No post submit found, but traversal post +submit set to 1!<BR>"); } } else { debugprint ("No post submit found, but traversal post subm +it set to 1!<BR>"); }; # # OK. # At this point in the game, the previous page has been disp +layed, # or the default page has been queued up. # # debugprint ("Setting Current_Page to '$nextpage'<BR>"); $Current_Page = $nextpage; } else { debugprint ("Nextpage is not defined... what happened here?<BR +>"); } # # These are 4 special vars that will get loaded for any page. # $$vars{pagetitle} is a reference to the description, I use # it to define titles in my html templates. # Will probably go away once I come to my sense. # # $$vars{current_page} is used to drive the RunPages subroutine. # very much needed. # # $$vars{error_level} is a way to pass the errorlevel onto the # HTML templates. This way you can have blocks like : # [% IF error_level %] Don't do that again! [% END %] # and so on. # # related to this is get_bit, which really should be unnecessary +, but # I can't seem to get tt2 to recognize bitwise ops. # having get_bit available to the HTML Template will allow users # to define bitflags in their errorcodes and check for them in # templates. # debugprint ("Setting \$\$vars info<BR>"); $$vars{pagetitle} =$pages{$Current_Page}{description}; $$vars{current_page}= $Current_Page; $$vars{error_level} = $errorlevel; $$vars{get_bit}=sub {if ($_[0] && $_[1]){return ((scalar shift) & +(scalar shift))}}; # why doesn't TT2 recognize bitwise ops? :( debugprint ("Hiding current_page -- $Current_Page<BR>"); param('Current_Page',$Current_Page); print hidden ("Current_Page", $Current_Page); debugprint ("Setting Template<BR>"); debugprint ("Checking for preprocess sub<BR>\n"); # # And now prepare to do any preprocessing for the real current p +age. # if (defined $pages{$Current_Page}{preprocess}) { $pages{$Current_Page}{preprocess}->(\$vars, \$errorlevel); } else { debugprint ("no preprocess sub found<BR>\n"); } # # show the template # my $template; $template= Template->new() || die( "Can't create template $!"); $template->process($pages{$Current_Page}{template}, $vars)|| die ( +"Template process failed: ", $template->error(), "\n"); debugprint ("<BR>Done printing template<BR>\n"); debugprint ("Cleaning up<BR>"); print end_form; print end_html; } sub set_errorlevel { $errorlevel = $_[0]; } sub get_errorlevel { return $errorlevel; } sub set_var { my $_key = shift; my $_val = shift; print "setting \$\$vars {$_key} to $_val"; $$vars {$_key} = $_val; } sub get_var { if (exists $$vars{$_[0]}) {return \$$vars{$_[0]}} else {return undef} } sub get_param { if (exists $$vars{params}{$_[0]}) {return \$$vars{params}{$_[0]}} else {return undef} } sub set_param { $$vars{params}{$_[0]} } # #this will print an html table showing # the structure of the assembled %pages # currently broken for pages that have # recursive references. :( # # sub dump_struct{ my $cp = $defaultpage; print "<CENTER>"; print "<TABLE BORDER=1>"; print "<TH>Page Name</TH><TH>children</TH><TH>Verified?</TH>"; dump_line ($cp); print "</TABLE>"; } sub dump_line{ my $cp=shift; if (keys %{$pages{$cp}{traverse_list}}){ foreach (sort keys %{$pages{$cp}{traverse_list}}) { print "<TR><TD>$cp</TD><TD>", ($_ eq $cp)?"<I>self</I> +":$_,"</TD><TD>", $pages{$cp}{traverse_list}{$_}?"yes":"no","</TD> +"; } foreach (sort keys %{$pages{$cp}{traverse_list}}) { unless ($_ eq $cp) {dump_line ($_)} } } else { print "<TR><TD>$cp</TD><TD COLSPAN=3>no children</td>"; } } # # Loads a file into a package. # # Courtesy Ben Tilly. # # node_id=52229 # sub load_lib { my $file = shift; my $pkg = shift; my $ret = eval "package $pkg; do '$file';"; if (not defined($ret)) { $@ and confess("Cannot parse '$file': $@"); $! and confess("Cannot load '$file': $!"); $@ and debugprint ("Cannot parse '$file': $@"); $! and debugprint ("Cannot load '$file': $!"); warn("Loading '$file' did not return a defined value"); } $ret; } sub debugprint { if (DEBUG) {print scalar localtime , " : ", @_, "\n"}; }

I encourage you to post any comments you have. I want to develop this into a professional quality module -- I've come up with some changes I want to make just explaining this, and I'd appreciate suggestions from the community at large.

Replies are listed 'Best First'.
Re (tilly) 1: Framework.pm 0.01a
by tilly (Archbishop) on May 06, 2001 at 05:12 UTC
    People often wish that PerlMonks did more code reviews.

    They want a special section for that.

    I think that this post is an excellent example of why it doesn't happen. This is code that boo_radley clearly put work and thought into. He asked directly for feedback. While plenty duly voted him up, so far nobody was willing to put their neck on the line and put out the energy to give him any.

    My opinion is that this happens because enough code to be useful for review is enough code to be more work than most people want to put out. Also when dealing with code that is meant to be good, and has nothing glaringly wrong with it, people don't generally feel comfortable criticizing.

    Unfortunately I don't have energy to follow this up with a real dialog. But I will put down a baker's dozen of things that I noticed and think could be done differently. I sincerely hope that someone else follows up with a third opinion on some of these issues (or anything else you see)...

    1. Why the prototypes? As you know I am not a fan of them, but since you clearly intentionally using them, I am curious what led you to do so.
    2. I prefer seeing stuff in @EXPORT_OK rather than @EXPORT. (Just like Exporter recommends.) Particularly if (like get_var and set_var) the names are generic with no relation to the module at hand.
    3. You do a lot of passing by reference and passing of references. Compared to the work it takes to process arguments, the work to pass them is minor. I prefer to not worry about that.
    4. A related API note. You seem to do (see the get and set methods) a lot of modification of variables through references. To my eyes this is a disguised global. OK, it isn't called a global, but the same locality of reference issue exists.
    5. If you are going to access by reference, I prefer to see $foo->{bar} rather than $$foo{bar} because that way the code reads more naturally "left to right".
    6. In your 2 InsertPage* methods I count the same list of 10 items appearing no less than 4 times. That is repeated information that can and should be abstracted into an array and used internally. Otherwise it is a potential maintainance problem. (It would also be nice if one of those methods called the other behind the scenes.)
    7. Some of your arguments should be able to default to reasonable things. You have 10 variables, some of which you admit are useless...
    8. I don't like passing errors in a global variable that may or may not get checked. I would suggest making those errors be raised. If the user page wants to it can catch them with an eval block (which is efficient). At the top level you can trap that and do something reasonable.
    9. Similarly numerical return codes tend to be a lot harder to track down when things go wrong than nicely formatted string errors. At the best the string gives you a description of what went wrong. At worse the contents of the string are easy to grep for. This is (of course) in addition to the fact that it is easier to type 13 when you meant 14 than it is to type Permission denied when you meant Bad address. (Example error messages taken from what I get for system errors 13 and 14 on my machine.)
    10. Your load_lib routine will probably not work as you intend since confess is a fatal error. (In my example code that you borrowed, I intended that result. The only soft error was an undefined return value without $! or $@ being set.)
    11. I would wonder about loading code into package main. I think it would be better to require the code and let the user make sure that their libraries are valid modules, which are loaded into the package that they should be. Something like this:
      # Load a package sub load_lib { my $pkg = shift; my $file = $pkg; $file =~ s/::/\//g; $file .= ".pm"; require $file; }
      Note that this does not import. If the modules are OO, then that should not matter. Note that if the module was already loaded, then the above will avoid redoing work.
    12. I would be inclined to have a worse dump_struct method. Just use Data::Dumper and print out a plain text page. That may not be pretty, but it is easy to do, handles difficult issues with recursive references et al, and is likely to be useful in debugging.
    13. In your example code, I notice no locking logic. I would suspect race conditions where your simple database handling will cause data corruption. For a personal journal, regular backups are good enough. But it still is an issue to be aware of.
      Tilly,
      I really do appreciate the time you took to provide these thoughts. For the moment, I will focus on the list you provided. If you have further interest in discussing what you mentioned in the preambled, I'd like that as well.

      1. Actually, I'm not so hot on prototypes myself. I misinterpreted some information in the cookbook to mean that they were required when creating a module... I asked about this in the CB one day, and have since learned that prototypes are not required for module creation. I have since dropped them.
      2. OK, I understand this, although I think I take it more as a criticism of my naming skills rather than methodology.
      3. Agreed. Although I do plan on removing the passing altogether, to allow the user to call get_* and set_* in order to remove the need shift in and set such variables. Alternately, I could just...
      4. Make these items globals and export them.
      5. Acknowledged, but largely as a issue of style. I find $$foo{bar} just as easy to read.
      6. Excellent idea. I'll put this in the todo list.
      7. Agreed, and I'll probably take errorhandler out of the structure; it was put in there to placate a coworker, and never was clear on how it would be used.
      8. I've never handled errors in this fashion, but it makes sense.
      9. I used numeric codes as a way to keep the templates clean, and because the idea is familiar to me. Additionally, I don't see support for regexes in tt2's directives, although I could replace the get_bit sub with something like match_string (string, regex_contents)..
        [% IF get_bit (error_level, 1) == 1 %] <FONT COLOR=RED> [% END %] versus
        [% IF match_string (error_string, "first name") == 1 %] <FONT COLOR=RE +D> [% END %]
        hmm...
      10. Yes, I had noticed this occuring when I had typos in modules so loaded.
      11. Noted. I really don't have anything to add, I'm not brushing you off.
      12. OK, I'll take a look at it, although I'll probably try to massage it back into an HTML table of some sort. :)
      13. This is true, too. I expected some flak on this since I realized my DBI stuff was pretty light; I was concentrating on debugging framework rather than making sure the csv got locked appropriately.
      Again, thanks for taking the time to turn a critical eye to my submission. I plan on incorporating much of what you've suggested into the source code.
Re: Framework.pm 0.01a
by nop (Hermit) on May 11, 2001 at 16:20 UTC
    Hi boo_radley,
    I've very much enjoyed your posts on this issue. I too am hacking together a crude prototype of web app system. Not quite as complex as yours, but I wanted to toss it into the mix. Sorry, few docs yet. And not much error checking. TT2 handles the HTML. I'm interested in comparing the two designs.

    After thinking about this, an important issue is the topology of the graph of the page navigation. For example, is the (sub)site a simple linear directed graph? Easy. Is it a tree? Somewhat harder. If a page can send users different subsequent pages based on what they do, the validation code needs to be specific for not only where the person is coming from, but where they are headed. Example: suppose you have a page that has two forms on it: buy a house, or buy a car. (This is probably bad UI, but let's ignore that.) The car fields are irrelevant if the user is doing the house application, and vice versa. So the validation (provided valid address? provided mortgage insurance number? vs. provided valid make model year? valid VIN?) depends on the page the user is leaving (INITIAL APP SCREEN) as well as where they are headed (CAR APP II, HOME APP II). Perhaps this isn't a large concern, as the UI can (should) be simplified and where each page "does less".

    Pardon the ramble, here's some code:
    use strict; use FindBin; use lib "$FindBin::Bin"; # push curdir onto @INC use App; use CGI qw(:cgi); use CGI::Carp; use Matrix::HTML; my $app = App->new(tmpl_path=> '/templates'); $app->run( start => { TEMPLATE => 'start.tt', TEMPLATE_VARS => sub { #CODEREF OR HASH REF return {name=>'foo bar', size=>'big'} }, PERSIST => [qw(fred wilma betty)], #ARRAYREF VALIDATE => sub { if (param('Color') eq 'Green') { croak "I hate green" } }, POSTSUBMIT => sub{ print "HELLO!!!!!!"}, NEXTPAGE => sub {return 'two'}, #CODEREF or STRING } , two => { TEMPLATE => 'two.tt', TEMPLATE_VARS => { x => Matrix::HTML->new([[431,442,3],[334,5,6],[7,8,4449]]), what_you_picked => param('Color'), size=>rand(10) } } );
    Here's the module, similar to CGI::Application:
    package App; use strict; use CGI::Carp; use CGI qw(:cgi); use Template; use FindBin; sub new { my $class = shift; my $self = { tmpl_path => '', mode_param => 'rm', default_mode => 'start' }; bless($self, $class); # set default values my %parms = @_; foreach my $p (keys %parms) {$self->set($p, $parms{$p});} return $self; } sub set { my ($self, $p, $v) = @_; croak "unknown parm: $p" unless exists($self->{$p}); $self->{$p} = $v; } sub get { my ($self, $p) = @_; croak "unknown parm: $p" unless exists($self->{$p}); return $self->{$p}; } sub run { my ($self, %logic) = @_; my $nextmode; # figure out which page was just submitted my $mode = &param($self->get('mode_param')); if (defined($mode)) { # is this mode valid? croak "unknown mode: $mode" unless defined($logic{$mode}); # determine next mode (coderef or string) if (ref($logic{$mode}{NEXTPAGE}) eq 'CODE') { $nextmode = &{$logic{$mode}{NEXTPAGE}}; } else { $nextmode = $logic{$mode}{NEXTPAGE}; } # run validator, if one exists (coderef) &{$logic{$mode}{VALIDATE}} if $logic{$mode}{VALIDATE}; # run postsubmit, if one exists (coderef) &{$logic{$mode}{POSTSUBMIT}} if $logic{$mode}{POSTSUBMIT}; } else { # if no mode specified, use default $nextmode = $self->get('default_mode'); } # now work on next page $mode = $nextmode; # load template vars, if any (coderef or hashref) my $tmpl_vars; if (ref($logic{$mode}{TEMPLATE_VARS}) eq 'CODE') { $tmpl_vars = &{$logic{$mode}{TEMPLATE_VARS}}; } if (ref($logic{$mode}{TEMPLATE_VARS}) eq 'HASH') { $tmpl_vars = $logic{$mode}{TEMPLATE_VARS}; } # get the persistant variables (arrayref) my %hidden; foreach my $h (@{$logic{$nextmode}{PERSIST}}) { $hidden{$h}=param($h); } # and add in the runmode variable $hidden{$self->get('mode_param')} = $nextmode; # chunk into template vars $tmpl_vars->{hidden} = \%hidden; # set up the template object my $T = Template->new({ INCLUDE_PATH =>$FindBin::Bin . $self->get('tmpl_path') }); # get the name of the template file my $template = $logic{$nextmode}{TEMPLATE}; # and spit out page $T->process($template, $tmpl_vars) || croak $T->error(); } 1;
    nop

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: CUFP [id://78183]
Approved by root
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others imbibing at the Monastery: (5)
As of 2024-04-19 20:07 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found