Beefy Boxes and Bandwidth Generously Provided by pair Networks
more useful options
PerlMonks 0.02

by bmcatt (Friar)
on Nov 21, 2001 at 01:56 UTC ( #126658=perlquestion: print w/replies, xml ) Need Help??

bmcatt has asked for the wisdom of the Perl Monks concerning the following question:

After much discussion with boo_radley about his 0.01a, we decided to embark on a revised version of

The initial goal was to do a few things:

  1. Convert to a more OO version of
  2. Provide for more explicit parameter validation
  3. Provide better integration with parameter passing and template invocation.

We've got an initial version which hasn't completed development, but should actually be functional to a large extent. Comments, etc., welcome.

The basic idea that we started with was that we were dealing with two separate objects/classes - a Page and a Collection.

The Page contains all of the knowledge about how the inputs should be processed, which page should be "next" and how the output should be generated.

Meanwhile, the Collection contains multiple Pages and provides the command and control logic on top of the Pages.

A sample snippet (very sample) would be:

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 .= "<html><body>You have logged in as " . $paramref->{'name'} . "</body></html>"; return $s; } $coll->page($p1); $coll->page($p2); $coll->defpage($p1); $coll->run(new CGI); print $coll->output;

While this is a contrived example, it should (hopefully) show some of the underlying power of the module.

The parts that we feel still definitely need to be implemented are:

  • Better error handling
  • Better parameter passing (that doesn't always go through the paramobj (now a CGI))
  • Creation of a derived Page that provides prepackaged validation routines
  • Creation of a sample template wrapper class. Currently, we're based on TT2, but would like to be able to support HTML::Template (and others?) through a wrapper on the templating engine instance.
  • Probably other things that we haven't thought of yet.

As mentioned above, we'd definitely appreciate thoughts, comments, suggestions on improvements, etc.

#! 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 I<Page>s which are bundled together into a I<Collection>. 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<undef>. The first C<$page> added to a Collection is the default page unless overriden through a call to C<defpage()>. =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<last resort> 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<TRUE>, 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<FALSE>, 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<run()>. =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 w +anting # 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 => '<br>' ); =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<TRUE>, it indicates that the subsequent postsubmit subref is to be called. Otherwise, the postsubmit is skipped and the traverse subref is called. B<Note>: 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<FALSE> 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<run()>. Called as: &preprocess($targetpage); The return value from C<&preprocess> is used as the return value from Collection's C<run()>. =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<output()> is called. =cut sub template ($$) { my $self = shift; if (@_) { $self->{_TMPL} = shift; } return $self->{_TMPL}; } =pod =item output(\&output) Assigns / retrieves the subref for the output generation or postprocessing. Called as: &output($targetpage, \%params, $output); C<$output> is either the output of the templating engine (if it is defined and a template exists for this page) or the output of the preprocessing step. If this subref is provided, the return value will be used as the return value from the Collection's C<output()>, otherwise the return value from the templating engine will be used. =cut sub output ($\&) { my $self = shift; if (@_) { $self->{_OUTPUT} = shift; } return $self->{_OUTPUT}; } =pod =item hparams(I<\@hidden_params>) Assigns / retrieves an arrayref containing the list of parameters which are to be turned into "hidden" fields for the templating or output processing. All of the hidden fields will be provided as a single parameter named C<_PAGE_HIDDEN>. In addition to the user-specified hidden parameters, the Collection will also be providing at least one other hidden parameter. At a minimum, the Collection will be providing C<_PAGE_CURRENT> which is used for page determination at the beginning of the C<run()>. If called in scalar context, C<hparams()> returns the arrayref. If called in list context, C<hparams()> will dereference the array ref and return the values as a list. =cut sub hparams { my $self = shift; if (@_) { $self->{_HPARAMS} = \@_; } my $paramref = $self->{_HPARAMS}; return ( wantarray ? @$paramref : $paramref ); } =pod =item aparams(I<\@available_params>) Assigns / retrieves an arrayref containing a list of parameters to be "made available" to the templating engine and output processing. Any parameters to be mmade available are copied verbatim into the parameter hashref which is passed to the templating engine and the page's output subref. If called in scalar context, C<aparams()> returns the arrayref. If called in list context, C<aparams()> will dereference the array ref and return the values as a list. =cut sub aparams { my $self = shift; if (@_) { $self->{_APARAMS} = \@_; } my $paramref = $self->{_APARAMS}; return ( wantarray ? @$paramref : $paramref ); } # Externally visible methods that are intended to be called by the # subrefs which are called through the Collection we belong to. =pod =item add_error($name, $message) B<Subref call> - intended to be called only from the subrefs when Collection invokes them. Add an error message to the phase-specific set of error messages, against the parameter C<$name>. Note that C<$name> does not have to actually refer to a real parameter name. However C<$name> will be used when the error messages are transfered to the templating engine or output subref in the parameter hash. Error messages for C<$name> will be passed in as the parameter C<_ERROR_$name>. Each processing step (:valid, :postsub, :traverse, :preproc) has its own set of error messages which are individually filled. Multiple errors for an individual C<$name> during the same processing step will be separated by the I<error separator>. =cut sub add_error { my $self = shift; my ($param, $message) = @_; my $errlist = $self->{_ERROR_LIST}->{$self->{_PHASE}}; if (exists $errlist->{$param}) { $errlist->{$param} .= $self->{_ERROR_SEP}; } $errlist->{$param} .= $message; } =pod =item error_sep($separator) Assigns / retrieves the string to be used between multiple error messages for the same parameter name. Note that C<error_sep()> may be set separately for the current and target pages and this may cause strange behaviour. =cut sub error_sep { my $self = shift; if (@_) { $self->{_ERROR_SEP} = shift; } return $self->{_ERROR_SEP}; } =pod =item errors(':any') =item errors(':any:valid') [or :any:postsub, :any:traverse, :any:prepr +oc] =item errors(':all') =item errors(':valid') [or :postsub, :traverse, :preproc] =item errors() B<Subref call> - intended to be called only from the subrefs when Collection invokes them. Retrieves the list of errors (or presence of errors) for different phases and in different forms. C<errors(':any')> (and the similar :any:I<type> form) return a boolean TRUE / FALSE indicating whether there are any errors present for all processing phases or for the particular (specified) type. C<errors(':all')> returns a hashref where the keys are the types (':valid', etc) and the values are hashrefs. The contained hashrefs are set up as C<$name =E<gt> $message(s)>. C<errors(':valid')> (and the similar forms) return a hashref of hashref where the single key in the containing hashref is the key that was passed in. C<errors()> returns a flattened single-level hash version of C<errors(':all')> where the top level of keys is removed. All entries are set up as C<$name =E<gt> $message(s)>. Multiple messages for the same C<$name> will be combined as for C<add_error()>. =cut sub errors { my $self = shift; my ($type) = @_; # Handle undef :type if (!$type) { my %errors; foreach (@ERROR_CLASSES) { my $sublist = $self->{_ERROR_LIST}->{$_}; foreach (keys %$sublist) { if (exists($errors{$_})) { $errors{$_} .= $self->{_ERROR_SEP}; } $errors{$_} .= $sublist->{$_}; } } return \%errors; } # $type eq ':any' or in (:any:valid, :any:postsub, etc.) if ($type =~ /^:any/) { $type =~ s/^:any//; if (!$type) { # Simple :any check foreach (@ERROR_CLASSES) { return 1 if %{$self->{_ERROR_LIST}->{$_}} }; return 0; } if (grep {/^$type$/} @ERROR_CLASSES) { return (%{$self->{_ERROR_LIST}->{$type}} && 1); } die "Foolish mortal... :any with invalid type $type"; } # :type eq ':all' if ($type eq ':all') { return $self->{_ERROR_LIST}; } # :type in (:valid, :postsub, :traverse, :preproc) if (grep {/^$type$/} @ERROR_CLASSES) { my %out = ( $type => \%{$self->{_ERROR_LIST}->{$type}} ); return \%out; } # XXX Better error handling... die "A pox on your errors call ($type)" if ($type); } #sub param ($$) { # my $self = shift; # my ($paramname) = @_; # # # XXX Implement the internal parameter passing. #} =pod =item paramobj() B<Subref call> - intended to be called only from the subrefs when Collection invokes them. Retrieves the parameter object which was originally passed in to the Collection's C<run()>. This is intended to allow a subref access to the parameters from the parameter object. =cut sub paramobj { my $self = shift; if (@_) { $self->{_PARAMOBJ} = shift; } return $self->{_PARAMOBJ}; } # Internally called routines. Don't look down here unless you really w +ant to # see some of the innards of Framework... Really... I warned you... sub _phase { my $self = shift; my $phase = shift; debugprint 2, "Page ", $self->{_NAME}, "->_phase change to $phase\ +n"; if ($phase eq ':undef') { $self->{_PARAMOBJ} = undef; $phase = ':runconfig'; # Force clear errors } if ($phase eq ':runconfig') { $self->{_ERROR_LIST} = { map { $_ => {} } @ERROR_CLASSES }; } if ($phase eq ':seterrors') { $self->{_ERROR_LIST} = $_[0]; } $self->{_PHASE} = $phase; } =pod =back =cut 1; __END__ # Below is the stub of documentation for your module. You better edit +it! =pod =cut

Replies are listed 'Best First'.
Re: 0.02
by uwevoelker (Pilgrim) on Nov 21, 2001 at 02:32 UTC

    Are you planning to integrate a data-class which retrieves/stores data on a database?

      Right now we're working on implementing templater methods so that the framework can work with any type of object oriented templating system, but I'm interested to hear about your idea -- are you talking about a generic database handle within the object (eg for submitting user forms and requests, and so on) , or do you mean something to handle the maintenance of the pages' structure?
        Yes, I'm talking about a generic database handle. You describe your data in a data definition language and the data-class handles the creation of the table, the data validation, the data convertion (e.g. for date-datatype) and of course the storage and retrieval. So your validator could contain a ref to a subroutine or an ref to an array containing fields to auto-validate (based upon the data description).
Re: 0.02
by perrin (Chancellor) on Nov 21, 2001 at 21:45 UTC
      Although I guess I could answer "TIMTOWTDI", that seems like a cop out. I'll try to enumerate my reasons as coherently as possible, so this node may be a bit longish.

      I wanted to force as little upon a designer as possible. When a client says to me "Why should I install Apache over my current server? And why do I need to install an XML parser for this site?" I can immediately discount Apache::PageKit (and Apache::* in general. Yes, some clients are that pushy and do get their way like this...).

      I can understand the comparisons to CGI::Application, especially when both modules have seemingly similar setup and execution methods, but ours allows programmers to explicitly compartmentalize the assorted parts of displaying a page (more on this below.), and allows the programmer an easy way to clone and modify pages. I will admit that I have only cursory experience with CGI::Application, though.

      Moving on, CGI::Application also enforces the use of HTML::Template files. I personally do not care for HTML::Template. So, I wanted to code something more flexible...
      I believe this will be the only module in its class to embrace and encourage the usage of any templating system, or none at all; the first release used TT2, and through Ben's efforts, this one will use HTML::Template objects as well as TT2 ones. We're preparing methods to allow interface to a wider array of templating systems, so that virtually ANY templater could be used. We're also implementing the ability to run without templates at all, because they can be cumbersome for some situations, or maybe just don't fit well in a given solution. Maybe you just like CGI and want to use it. Like I said, I want to encumber programmers as little as possible.
      The same thing goes for data validation; there's a handful of commonly referenced data validation modules on cpan. Which one do you like? Maybe your data validation occurs when data's submitted to the database, and you don't need any perl for it at all, you just want to pay attention to the db handle's return value. It'll be your gig.

      Of course, I am not against any other module. I don't think any of them are dangerous, or badly written, so plase don't read anything into this project. In fact, personal experince tells me that Jesse Erlbaum has a thriving and active interest in the CGI::App community, and he's always been responsive to his user's needs.

      So what is the strength of the Framework concept?

      I believe that it's in the way Framework's pages will flow : there is a well-defined order that things will occur in.
      An application will always have the opportunity to (this is sort of a gloss-over - read the parent node if you want the full details.)
      • determine state
      • perform data validation
      • template preparation
      • preprocessing
      • template display
      • post processing

      This, in a nutshell, is one of the strengths -- it does what it says, provides a framework for your web app, and not just as a collection of loosely related subroutines and templates, it's structuring makes it unique. If a page has error checking, it'll happen consistently. If a page has preprocessing requirements, you only need to worry about the requirements, and not how to wedge them into the flow of the application, or how to return errors from each of the above phases, or how to handle parameter processing.
      So, there are similarities between this and the other modules, yes. Their goals are similar, but I think the execution will be where Framework shines.
        It still sounds quite a bit like CGI::MxScreen, but maybe less prescriptive about what other modules it works with. Being flexible can be tough, because an application framework that doesn't assume anything about choice of template tool or validating tool can't offer as much automation to the developer using it. Maybe you can find a good middle ground.

        As far as CGI::Application goes, I don't see why you don't just extend that rather than starting from scratch. It seems like it wouldn't be hard to change CGI::App to work with TT2. The HTML::Template integration is minimal.

      Well, agreed somewhat on CGI::Application. To be honest, I hadn't seen either of the other two when I started thinking about this and found boo_radley's version posted here.

      The reason I originally started trying to do something like this was that I'd found CGI::Application but didn't like that it didn't have either integrated validation or integrated support for template management. Then I found HTML::Template and it's another piece. Then there's Data::FormValidator, etc.

      It seems like everyone wants to solve a piece of the problem without necessarily solving the entire problem (or providing a good set of glue to make all the pieces fit together). This module provides templating, that module provides for individual field validation, this other module provides for flow control.

      That's all wonderful (and I'm really not getting down on the contributions of others), but with a large collection of modules, none of which do the entire task, and potentially non-trivial glue to write, it made me think there had to be a better way.

      I'm definitely not saying that this ( is the better way. I'm going to sit and take a long look at the modules mentioned, but I think I'll be able to stand by my previous statement that most take a piecemeal approach to this problem.

      Heck, maybe the right way to solve this is to come up with the glue that binds an orthogonal yet complete bunch of the pieces together. Dunno... That wasn't the swamp I was looking to drain. :-)

        > I'm going to sit and take a long look at the modules mentioned

        And when you do this, please also look at and the other frameworks mentioned on princepawn's homenode (like OpenFrame, OpenInteract, OpenThought, CGI::XMLApplication and so on).
        The tools I mentioned are really meant as glue code between the various pieces. They're trying to do the same thing you are. It might be more efficient to look at these and choose the one that's closest to what you want, and then add the additional features you need to it. That will give you an instant set of testers for your work.

        I would suggest giving OpenInteract a look as well.

Log In?

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

How do I use this? | Other CB clients
Other Users?
Others studying the Monastery: (6)
As of 2023-03-20 10:00 GMT
Find Nodes?
    Voting Booth?
    Which type of climate do you prefer to live in?

    Results (59 votes). Check out past polls.