NAME

CGI::Ex::App - Full featured (within reason) application builder.

DESCRIPTION

Fill in the blanks and get a ready made CGI. This module is somewhat similar in spirit to CGI::Application, CGI::Path, and CGI::Builder and any other "CGI framework." As with the others, CGI::Ex::App tries to do as much as possible, in a simple manner, without getting in the developer's way. Your milage may vary.

SYNOPSIS

More examples will come with time. Here are the basics for now.

#!/usr/bin/perl -w

MyApp->navigate;
 # OR you could do the following which cleans
 # circular references - useful for a mod_perl situation
 # MyApp->navigate->cleanup;
exit;

package MyApp;
use strict;
use base qw(CGI::Ex::App);
use CGI::Ex::Dump qw(debug);

sub valid_paths { return {success => 1, js => 1} }
  # default_step (main) is a valid path
  # note the inclusion of js step for js_validation

# base_dir_abs is only needed if default print is used
# template toolkit needs an INCLUDE_PATH
sub base_dir_abs { '/tmp' }

sub main_file_print {
  # reference to string means ref to content
  # non-reference means filename
  return \ "<h1>Main Step</h1>
  <form method=post name=[% form_name %]>
  <input type=text name=foo>
  <span style='color:red' id=foo_error>[% foo_error %]</span><br>
  <input type=submit>
  </form>
  [% js_validation %]
  <a href='[% script_name %]?step=foo'>Link to forbidden step</a>
  ";
}

sub post_print { debug shift->{history} } # show what happened

sub main_file_val {
  # reference to string means ref to yaml document
  # non-reference means filename
  return \ "foo:
    required: 1
    min_len: 2
    max_len: 20
    match: 'm/^([a-z]\\d)+[a-z]?\$/'
    match_error: Characters must alternate letter digit letter.
    \n";
}

sub main_finalize {
  my $self = shift;

  debug $self->form, "Do something useful with form here";

  ### add success step
  $self->append_path('success');
  $self->set_ready_validate(0);
  return 1;
}

sub success_file_print {
  \ "<h1>Success Step</h1> All done";
}

sub hash_common { # used to include js_validation
  my $self = shift;
  my $step = shift;
  return $self->{hash_common} ||= {
    script_name   => $ENV{SCRIPT_NAME},
    js_validation => $self->run_hook($step, 'js_validation'),
    form_name     => $self->run_hook($step, 'form_name'),
  };
}

__END__

Note: This example would be considerably shorter if the html file (file_print) and the validation file (file_val) had been placed in separate files. Though CGI::Ex::App will work "out of the box" as shown it is more probable that any platform using it will customize the various hooks to their own tastes (for example, switching print to use a system other than Template::Toolkit).

HOOKS / METHODS

Hooks are basically methods calls that look for a variety of method names. See the discussion under the method named "hook" for more details. Hooks and methods are looked for in the following order:

Method ->new

Object creator. Takes a hash or hashref.

Method ->init

Called by the default new method. Allows for any object initilizations.

Method ->form

Returns a hashref of the items passed to the CGI. Returns $self->{form}. Defaults to CGI::Ex::get_form.

Method ->navigate

Takes a class name or a CGI::Ex::App object as arguments. If a class name is given it will instantiate an object by that class. All returns from navigate will return the object.

The method navigate is the main loop runner. It figures out the path and runs all of the appropriate hooks for each step of the path. Once all steps in the path run successfully, it will call it self with a path set to [$self->default_step] to allow for a default main path to run.

The basic outline of navigation is as follows (the default actions for hooks are shown):

navigate {

  ->path (get the path steps)
     # DEFAULT ACTION
     # look in $ENV{'PATH_INFO'}
     # look in ->form for ->step_key

  ->pre_loop
     # navigation stops if true

  ->valid_steps (get list of valid paths)

  foreach step of path {

    # check that path is valid

    ->morph
      # DEFAULT ACTION
      # check ->allow_morph
      # check ->allow_nested_morph
      # ->morph_package (hook - get the package to bless into)
      # ->fixup_after_morph if morph_package exists

    ->pre_step (hook)
      # skips this step if true

    ->prepare (hook - defaults to true)

    ->info_complete (hook - ran if prepare was true)
      # DEFAULT ACTION
      # ->ready_validate (hook)
      # return false if ! ready_validate
      # ->validate (hook)
      #   ->hash_validation (hook)
      #     ->file_val (hook - uses base_dir_rel, name_module, name_step, ext_val)
      #   uses CGI::Ex::Validate to validate the hash
      # returns true if validate is true

    ->finalize (hook - defaults to true - ran if prepare and info_complete were true)

    if ! ->prepare || ! ->info_complete || ! ->finalize {
      ->hash_form (hook)
      ->hash_fill (hook)
      ->hash_errors (hook)
      ->hash_common (hook)

      # merge common, errors, and form

      ->print (hook - passed current step, merged hash, and fill)
        # DEFAULT ACTION
        # ->file_print (hook - uses base_dir_rel, name_module, name_step, ext_print)
        # ->template_args
        # Processes the file with Template Toolkit
        # Fills the any forms with CGI::Ex::Fill
        # Prints headers and the content

      ->post_print (hook - used for anything after the print process)

      # return from navigation
    }

    ->post_step (hook)

    ->unmorph (actually called any time the step exits the loop)
      # DEFAULT ACTION
      # ->fixup_before_unmorph if blessed to previous package

  } end of step foreach

  ->post_loop
    # navigation stops if true

  ->default_step
  ->navigate (passed the new default step)

} end of navigation

# dying errors will run the ->handle_error method
Method ->path

Return an arrayref (modifyable) of the steps in the path. For each step the remaining hooks can be run. Hook methods are looked up and ran using the method "run_hook" which uses the method "hook" to lookup the hook. A history of ran hooks is stored in $self->{history}. Default will be a single step path looked up in $form->{path} or in $ENV{PATH_INFO}. By default, path will look for $ENV{'PATH_INFO'} or the value of the form by the key step_key. For the best functionality, the arrayref returned should be the same reference returned for every call to path - this ensures that other methods can add to the path (and will most likely break if the arrayref is not the same). If navigation runs out of steps to run, the default step found in default_step will be run.

Method ->default_step

Step to show if the path runs out of steps. Default value is the 'default_step' property or the value 'main'.

Method ->step_key

Used by default to determine which step to put in the path. The default path will only have one step within it

Method ->set_path

Arguments are the steps to set. Should be called before navigation begins. This will set the path arrayref to the passed steps.

Method ->append_path

Arguments are the steps to append. Can be called any time. Adds more steps to the end of the current path.

Method ->replace_path

Arguments are the steps used to replace. Can be called any time. Replaces the remaining steps (if any) of the current path.

Method ->insert_path

Arguments are the steps to insert. Can be called any time. Inserts the new steps at the current path location.

Method ->valid_steps

Returns a hashref of path steps that are allowed. If step found in default method path is not in the hash, the method path will return a single step "forbidden" and run its hooks. If no hash or undef is returned, all paths are allowed (default). A key "forbidden_step" containing the step that was not valid will be placed in the stash. Often the valid_steps method does not need to be defined as arbitrary method calls are not possible with CGI::Ex::App.

Method ->previous_step, ->current_step, ->next_step

Return the previous, current, and next step name - useful for figuring out where you are in the path.

Method ->pre_loop

Called right before the navigation loop is started. At this point the path is set (but could be modified). The only argument is a reference to the path array. If it returns a true value - the navigation routine is aborted.

Method ->run_hook

Calls "hook" to get a code ref which it then calls and returns the result. Arguments are the same as that for "hook".

Method ->hook

Arguments are a pathstep name, a hook name, and an optional code sub or default value (default value will be turned to a sub) (code sub will be called as method of $self).

my $code = $self->hook('main', 'info_complete', sub {return 0});
### will look first for $self->main_info_complete;
### will then look  for $self->info_complete;
### will then run       $self->$default_passed_sub; # sub {return 0}
Method ->handle_error

If anything dies during execution, handle_error will be called with the error that had happened. Default is to debug the error and path history.

Method ->morph

Allows for temporarily "becoming" another object type for the execution of the current step. This allows for separating some steps out into their own packages. Morph will only run if the method allow_morph returns true. The morph call occurs at the beginning of the step loop. A corresponding unmorph call occurs before the loop is exited. An object can morph several levels deep if allow_nested_morph returns true. For example, an object running as Foo::Bar that is looping on the step "my_step" that has allow_morph = 1, will do the following: call the hook morph_package (which would default to returning Foo::Bar::MyStep in this case), translate this to a package filename (Foo/Bar/MyStep.pm) and try and require it, if the file can be required, the object is blessed into that package. If that package has a "fixup_after_morph" method, it is called. The navigate loop then continues for the current step. At any exit point of the loop, the unmorph call is made which reblesses the object into the original package.

It is possible to call morph earlier on in the program. An example of a useful early use of morph would be as in the following code:

sub init {
  my $self = shift;
  $self->SUPER::init;

  return if ! $ENV{'PATH_INFO'};
  my $info = ($ENV{'PATH_INFO'} =~ s|^/(\w+)||) ? $1 : return;
  $self->morph($info);
}

If this code was in a module Base.pm and the cgi running was cgi/base and called:

Base->navigate->unmorph;
# OR - for mod_perl resident programs
Base->navigate->unmorph->cleanup;

and you created a sub module that inherited Base.pm called Base/Ball.pm -- you could then access it using cgi/base/ball. You would be able to pass it steps using either cgi/base/ball/step_name or cgi/base/ball?step=step_name - Or Base/Ball.pm could implement its own path. It should be noted that if you do an early morph, it is suggested to provide a call to unmorph. And if you want to let your early morphed object morph again - you will need to provide

sub allow_nested_morph { 1 }

With allow_nested_morph enabled you could create the file Base/Ball/StepName.pm which inherits Base/Ball.pm. The Base.pm, with the custom init and default path method, would automatically morph us first into a Base::Ball object (during init) and then into a Base::Ball::StepName object (during the navigation loop).

Method ->unmorph

Allows for returning an object back to its previous blessed state. This only happens if the object was previously morphed into another object type. Before the object is reblessed the method "fixup_before_unmorph" is called if it exists.

Method ->allow_morph

Boolean value. Specifies whether or not morphing is allowed. Defaults to the property "allow_morph" if found, otherwise false.

Method ->allow_nested_morph

Boolean value. Specifies whether or not nested morphing is allowed. Defaults to the property "allow_nested_morph" if found, otherwise false.

Hook ->morph_package

Used by morph. Return the package name to morph into during a morph call. Defaults to using the current object type as a base. For example, if the current object running is a Foo::Bar object and the step running is my_step, then morph_package will return Foo::Bar::MyStep.

Hook ->pre_step

Ran at the beginning of the loop before info_compelete is called. If it returns true, execution of navigate is returned and no more steps are processed.

Hook ->prepare

Defaults to true. A hook before checking if the info_complete is true.

Hook ->info_complete

Checks to see if all the necessary form elements have been passed in. Calls hooks ready_validate, and validate. Will not be run unless prepare returns true (default).

Hook ->finalize

Defaults to true. Used to do whatever needs to be done with the data once prepare has returned true and info_complete has returned true. On failure the print operations are ran. On success navigation moves on to the next step.

Hook ->ready_validate

Should return true if enough information is present to run validate. Default is to look if $ENV{'REQUEST_METHOD'} is 'POST'. A common usage is to pass a common flag in the form such as 'processing' => 1 and check for its presence - such as the following:

sub ready_validate { shift->form->{'processing'} }
Method ->set_ready_validate

Sets that the validation is ready to validate. Should set the value checked by the hook ready_validate. The following would complement the processing flag above:

sub set_ready_validate { shift->form->{'processing'} = shift }
Hook ->validate

Runs validation on the information posted in $self->form. Uses CGI::Ex::Validate for the validation. Calls the hook hash_validation to load validation information. Should return true if enough information is present to run validate. Errors are stored as a hash in $self->{hash_errors} via method add_errors and can be checked for at a later time with method has_errors (if the default validate was used).

Upon success, it will look through all of the items which were validated, if any of them contain the keys append_path, insert_path, or replace_path, that method will be called with the value as arguments. This allows for the validation to apply redirection to the path. A validation item of:

{field => 'foo', required => 1, append_path => ['bar', 'baz']}

would append 'bar' and 'baz' to the path should all validation succeed.

Hook ->hash_validation

Returns a hash of the validation information to check form against. By default, will look for a filename using the hook file_val and will pass it to CGI::Ex::Validate::get_validation. If no file_val is returned or if the get_validation fails, an empty hash will be returned. Validation is implemented by ->vob which loads a CGI::Ex::Validate object.

Hook ->file_val

Returns a filename containing the validation. Adds method base_dir_rel to hook name_module, and name_step and adds on the default file extension found in $self->ext_val which defaults to the global $EXT_VAL (the property $self->{ext_val} may also be set). File should be readible by CGI::Ex::Validate::get_validation.

Hook ->js_validation

Will return Javascript that is capable of validating the form. This is done using the capabilities of CGI::Ex::Validate. This will call the hook hash_validation which will then be encoded into yaml and placed in a javascript string. It will also call the hook form_name to determine which html form to attach the validation to. The method js_uri_path is called to determine the path to the appropriate yaml_load.js and validate.js files. If the method ext_val is htm, then js_validation will return an empty string as it assumes the htm file will take care of the validation itself. In order to make use of js_validation, it must be added to either the hash_common or hash_form hook (see examples of hash_common used in this doc).

Hook ->form_name

Return the name of the form to attach the js validation to. Used by js_validation.

Method ->js_uri_path

Return the URI path where the CGI/Ex/yaml_load.js and CGI/Ex/validate.js files can be found. This will default to "$ENV{SCRIPT_NAME}/js" if the path method has not been overridden, otherwise it will default to "$ENV{SCRIPT_NAME}?step=js&js=" (the latter is more friendly with overridden paths). A default handler for the "js" step has been provided in "js_pre_step" (this handler will nicely print out the javascript found in the js files which are included with this distribution - if valid_steps is defined, it must include the step "js" - js_pre_step will work properly with the default "path" handler.

Hook ->hash_form

Called in preparation for print after failed prepare, info_complete, or finalize. Should contain a hash of any items needed to be swapped into the html during print.

Hook ->hash_fill

Called in preparation for print after failed prepare, info_complete, or finalize. Should contain a hash of any items needed to be filled into the html form during print. Items from hash_form will be layered on top during a print cycle.

Hook ->hash_errors

Called in preparation for print after failed prepare, info_complete, or finalize. Should contain a hash of any errors that occured. Will be merged into hash_form before the pass to print. Eash error that occured will be passed to method format_error before being added to the hash. If an error has occurred, the default validate will automatically add {has_errors =>1}. To the error hash at the time of validation. has_errors will also be added during the merge incase the default validate was not used.

Hook ->hash_common

A hash of common items to be merged with hash_form - such as pulldown menues. By default it is empty, but it would be wise to add the following to allow for js_validation (if needed):

sub hash_common {
  my $self = shift;
  my $step = shift;
  return $self->{hash_common} ||= {
    script_name   => $ENV{SCRIPT_NAME},
    js_validation => $self->run_hook($step, 'js_validation'),
    form_name     => $self->run_hook($step, 'form_name'),
  };
}
Hook ->name_module

Return the name (relative path) that should be prepended to name_step during the default file_print and file_val lookups. Defaults to base_name_module.

Hook ->name_step

Return the step (appended to name_module) that should used when looking up the file in file_print and file_val lookups. Defaults to the current step.

Hook ->file_print

Returns a filename of the content to be used in the default print hook. Adds method base_dir_rel to hook name_module, and name_step and adds on the default file extension found in $self->ext_print which defaults to the global $EXT_PRINT (the property $self->{ext_print} may also be set). Should be a file that can be handled by hook print.

Hook ->print

Take the information and print it out. Default incarnation uses Template. Arguments are: step name, form hashref, and fill hashref.

Hook ->post_print

A hook which occurs after the printing has taken place. Is only run if the information was not complete. Useful for printing rows of a database query.

Hook ->post_step

Ran at the end of the step's loop if prepare, info_complete, and finalize all returned true. Allows for cleanup. If a true value is returned, execution of navigate is returned and no more steps are processed.

Method ->post_loop

Ran after all of the steps in the loop have been processed (if prepare, info_complete, and finalize were true for each of the steps). If it returns a true value the navigation loop will be aborted. If it does not return true, navigation continues by then running $self->navigate({path => [$self->default_step]}) to fall back to the default step.

Method ->stash

Returns a hashref that can store arbitrary user space data without clobering the internals of the Application.

Method ->add_property

Takes the property name as an argument. Creates an accessor that can be used to access a new property. Calling the new accessor with an argument will set the property. Using the accessor in an assignment will also set the property (it is an lvalue). Calling the accessor in any other way will return the value.

Method ->cleanup

Can be used at the end of execution to tear down the structure. Default method starts a cleanup_cross_references call.

Method ->cleanup_cross_references

Used to destroy links in nested structures. Will spider through the data structure of the passed object and remove any blessed objects that are no weakly referenced. This means if you have a reference to an object in a global cache, that object should have its reference weakened in the global cache. Requires Scalar::Util to function. Use of this function is highly recommended in mod_perl environments to make sure that there are no dangling objects in memory. There are some global caches that can't be fixed (such as Template::Parser's reference to Template::Grammar in the Template::Toolkit). For these situations there is a %CLEANUP_EXCLUDE hash that contains the names of Object types to exclude from the cleanup process. Add any such global hashes (or objects with references to the global hashes) there.