CGI-MxScreen

 view release on metacpan or  search on metacpan

MxScreen.pm  view on Meta::CPAN


	$screen = $self->display($screen, $args, $stdout);
	$log->debug(\&log_inc_times, $self, "\"%s\" display", $screen->name);

	#
	# Snapshot current time and last modification date of the
	# scriptright before saving context.  That fields can be used to
	# check for session validity.
	#

    $self->ctx->{'time'} = time;
    $self->ctx->{'script_date'} = (stat($0))[9]; 

	#
	# Cleanup context to avoid saving transient data
	#

    &{$coderef}() if defined $coderef; # TBR

    for my $f (@{$self->context_root->[SCREEN_FIELD]}) {
        DASSERT $f->isa('CGI::MxScreen::Form::Field');
        $f->cleanup();
    }
    for my $b (@{$self->context_root->[SCREEN_BUTTON]}) {
        DASSERT $b->isa('CGI::MxScreen::Form::Button');
        $b->cleanup();
    }

	#
	# If STDOUT was bufferd, the context must be emitted explicitely
	# between the header of the form and the remaining data.
	#

	if (defined $stdout) {
		my $context = $self->session->save;
		$stdout->print_all($context);
		untie_stdout();
	} else {
		print $self->session->save;
	}

	$log->debug(\&log_inc_times, $self, "context save");

	#
	# Emit CGI trailers.
	#

	print CGI::endform;

	my $layout = $self->layout;
	$layout->postamble;
	$layout->end_HTML;

    return DVOID;
}

#
# ->compute_screen
#
# Compute target screen, and run and enter/leave hooks if we change screens.
# This routine does not display anything, but runs all the action callbacks.
#
# Returns new screen object, and a ref to the argument list.
#
sub compute_screen {
	DFEATURE my $f_;
	my $self = shift;
    my ($current_state, $previous_state, $new_state);
    my ($origin_name, $target_name, @arg_list);
    my $screen;
	my $errors = 0;
    my $ctx = $self->ctx;

   
    # get the current state from the context its format can be either
    # 'screen_name' or ['screen_name', @arg_list]. 'screen_name' is the
    # symbol key given to a screen name into the given screen list (at
    # make time) and @arg_list is a list of arg to pass to the display
    # routine of the screen.

    $current_state = $self->initial_state;
    $current_state = $ctx->{'current_state'} if
      (defined $ctx->{'current_state'});

    $previous_state = $current_state;
    $new_state = $current_state;

    #
    # Compute the destination and process the associated actions when
    # a button has been detected as pressed (during the make method).
	#
	# If we could not identify a button that was pressed, we'll simply
	# remain in the current state and re-display the form unless there was
	# a default button recorded in the previous screen.
    #

	my $button_pressed = $self->button_pressed;

	if ($ctx->{'log_cnt'} && !defined $button_pressed) {

		#
        # Create the previous screen to lookup for a default button
		#

        ($origin_name) = $self->scatter($previous_state);
        $screen = $self->make_screen($origin_name);
		my $default = $screen->default_button;

		if (defined $default) {
			$button_pressed = $self->{_button_pressed} = $default;
			$self->log->warning("no button pressed, using default \"%s\"",
				$default->value);
		} else {
			$self->log->error(
				"no button pressed, no default, will stay in same state");
		}
	}

    if (defined $button_pressed) {
     
		#

MxScreen.pm  view on Meta::CPAN

=item *

It has an object-oriented design.  Each screen is an object inheriting from
C<CGI::MxScreen::Screen> and redefining the C<display> routine, at least.
There are also C<enter> and C<leave> hooks for each screen.
Each created screen object is made persistent accross the whole session.
See L<CGI::MxScreen::Screen> for the full interface.

=item *

Any script output done before the screen's C<display> routine is called
will be trapped and discarded (with logging showing the place where such a
violation occurs).  This architecturally enforces proper application behaviour.
Furthermore, by default, the whole output is buffered until it is
time to save the context, thereby protecting against further submits
with a partially received form on the browser side, and also strengthening
the protection when the application uses bounce exceptions to jump into
another state.

=item *

Each CGI parameter (form control) can be given an explicit storage indication
(i.e. how the application should dispose of the value), a validation routine,
and an on-the-fly patching routines (to normalize values, for instance).
Each parameter may also be given a mandatory status, causing an error when
it is not filled.
See L<CGI::MxScreen::Form::Field> for more information.

=item *

There is a global hash that is made available to all screens and which is
made persistent accross the whole session.  By default, every key access
to that hash is checked to prevent typos, and reading an unknown key is
a fatal error (at run-time, unfortunately).

=item *

There are layout hooks allowing the generation of a common preamble and
postamble section, common to a group of scripts.  See L<CGI::MxScreen::Layout>
for details.

=item *

The framework can be configured by loading a configuration Perl script,
allowing easy sharing of the settings among a set of scripts, with
possible local superseding on a script basis.  See L<CGI::MxScreen::Config>
for details.

=item *

All error logging is done via C<Log::Agent>, and application logging is
done via C<Log::Agent::Logger>, which ensures the maximum flexibility.
Logfile rotation is also supported via C<Log::Agent::Rotate>.
Configuration of the various logging parameters is done via the
C<CGI::MxScreen::Config> interface.

=item *

C<CGI::MxScreen> uses C<Carp::Datum> internally.  If you have chosen to
install a non-stripped version, you may trace parts of the module to better
understand what is going on with the various callbacks you register.

=back

=head2 Flow

Here is a high-level description of the processing flow when issuing requests
to a C<CGI::MxScreen> script:

=over 4

=item *

An initial log tracing the user (if HTTP authentication is used), the
time since the session started, the elapsed time since the previous display,
and the CGI query string is emitted.

=item *

The session context is retrieved if any, otherwise a new one is created.
The context holds the various screen objects, the submit buttons and other
form fields descriptions, plus all the other data stored within the
persistent global hash.

=item *

Input parameters are processed, following the directives held within the
session to validate and optionally store them in some place.
If an error is detected, the application remains in the same state and
the previous screen is redisplayed.

=item *

If no error occurred during parameter processing, the target state is computed
based on the descriptions attached to the button that was pressed.  The
state can be given statically, or computed by a routine.
The determined target state is composed of a screen object, plus some optional
arguments that are to be given to its C<display> routine.
Any processing action attached to the button is also run at that point.

=item *

The transition is logged, tracing the pressed button, the previous state
and the new one.

=item *

If a screen change occurs (i.e. the new screen to display is not the same
as the previously displayed one), the C<leave> routine is called on the
old screen and C<enter> is called on the new one.

=item *

The enclosing form setting is emitted, and the screen's C<display> routine
is called to actually generate the form's content.  Before they output
anything, screens are allowed to request the bouncing to some other state,
based on some local information (but if output buffering is configured, any
spurious output from the old screen will be cleanly discarded).
Any other exception that can occur during C<display> is trapped and cleanly
logged, before displaying an internal error message.

MxScreen.pm  view on Meta::CPAN

 68     );
 69 

We declare a button named I<Back>, which will bring us back to the screen
we were when we sprang into the current screen.  That's what C<spring_screen>
is about: it refers to the previous stable screen.  Here, since there is
no possibility to remain in the current screen, it will be the previous screen.
But if we had a I<redraw> button like we had in the I<Color> screen, which
would make a transition to the same state, then C<spring_screen> will still
correctly point to C<Color>, whereas C<previous_screen> would be C<Weekday>
in that case.

 70     print submit($back->properties);
 71 }
 72 

This closes the C<display()> routine by generating the sole submit button
for that screen.

 73 package main;
 74 

We now leave the screen definition and enter the main part, where the
C<CGI::MxScreen> manager gets created and invoked.  In real life, the code
for screens would not be inlined but stored in a dedicated file, one file
for each class, and the CGI script would only contain the following code,
plus some additional configuration.

 75 require CGI::MxScreen;
 76 

We're not "using" it, only "requiring" since we're creating an object,
not using any exported routine.

 77 my $manager = CGI::MxScreen->make(
 78     -screens    =>
 79         {
 80             'Color'     => [-class => 'Color',   -title => "Choose Color" ],
 81             'Weekday'   => [-class => 'Weekday', -title => "Choose Day" ],
 82         },
 83     -initial    => ['Color'],
 84 );
 85 

The states of our state machine are described above.  The keys of the
C<-screens> argument are the valid state names, and each state name is
associated with a class, and a screen title.  This screen title will be
available to each screen with C<$self-E<gt>title>, but there's no
obligation for screens to display that information.  However, the manager
needs to know because when the C<display()> routine for the script is called,
the HTML header has already been generated, and that includes the title.

The act of creating the manager object raises some underlying processing:
the session context is retrieved, incoming parameters are processed and
silently validated.

 86 $manager->play();
 87 

This finally launches the state machine: the next state is computed, action
callbacks are fired, and the target screen is displayed.

=head2 More Readings

To learn about the interface of the C<CGI::MxScreen> manager object,
see L<"INTERFACE"> below.

To learn about the screen interface, i.e. what you must implement when you
derive your own objects, what you can redefine, what you should not override
(the other features that you cannot redefine, so to speak), please
read L<CGI::MxScreen::Screen>.

To learn more about the configuration options, see L<CGI::MxScreen::Config>.

For information on the processing done on recorded fields, read
L<CGI::MxScreen::Form::Field> and L<CGI::MxScreen::Form::Utils>.

For information on the state transitions that can be recorded, and the
associated actions, see L<CGI::MxScreen::Form::Button>.

The various session management schemes offered are described in
L<CGI::MxScreen::Session::Medium>.

The layering hooks allowing you to control where the generated HTML for
the current screen goes in your grand formatting scheme are described
in L<CGI::MxScreen::Layout>.

Finally, the extra HTML-generating routines that are not implemented by
the C<CGI> module are presented in L<CGI::MxScreen::HMTL>.

=head1 SPECIFIC DATA TYPES

This sections documents in a central place the I<state> and I<callback>
representations that can be used throughout the C<CGI::MxScreen> framework.

Those specifications must be serializable, therefore all callbacks
are expressed in various symbolic forms, avoiding code references.

Do not forget that I<all> the arguments you specify in callbacks and screens
get serialized into the context.  Therefore, you must make sure your
objects are indeed serializable by the serializer (which is C<Storable>
by default, well, actually C<CGI::MxScreen::Serializer::Storable>, which is
wrapping the C<Storable> interface to something C<CGI::MxScreen> understands).
See L<CGI::MxScreen::Config> to learn how to change the
serializer, and L<CGI::MxScreen::Serializer> for the interface it must
follow.

=head2 States

A state is a screen name plus all the arguments that are given to its
C<display()> routine.  However, the language used throughout this
documentation is not too strict, and we tend to blurr the distinction between
a state and a screen by forgetting about the parameters.  That is because,
in practice, the parameters are simply there to offer a slight variation
of the overall screen dispay, but it is fundamentally the same screen.

Anyway, a state can be either given as:

=over 4

=item *

A plain scalar, in which case it must be the name of a screen, as
configured via C<-screens> (see L<Creation Routine> below), and the
screen's C<display()> routine will be called without any parameter.

=item *

An array ref, whose first item is the screen name, followed by
arguments to be given to C<display()>.  For instance:

	["Color", "blue"]

would represent the state obtained by calling C<display("blue")> on the
screen object known as I<Color>.

=back

=head2 Callbacks

When an argument expects a I<callback>, you may provide it under the
foloowing forms.

=over 4

=item *

As a scalar name, e.g. C<'validate'>.

The exact interpretation of this form depends on the object where you
specify it.  Withing a C<CGI::MxScreen::Form::Button>, it specifies a
routine to call on the screen object, without any user parameter.  However,
within a C<CGI::MxScreen::Form::Field>, it could be a routine to lookup
within the utility namespaces.  More on the latter in L<Utility Path>.

=item *

As a list reference, starting with a scalar name:

MxScreen.pm  view on Meta::CPAN

=item C<-timeout> => I<seconds>

Optional, defines a session timeout, which will be enforced by C<CGI::MxScreen>
when retrieving the session context.  It must be smaller than the session
cleaning timout, if sessions are not stored within the browser.

When the session is expired, there is an error message stating so and the
user is invited to restart a new session.

=item C<-version> => I<string>

Defines the script's version.  This is I<your> versioning scheme, which has
nothing to do with the one used by C<CGI::MxScreen>.

You should use this to track changes in the screen objects that would
make deserialization of previous ones (from an old session) improper.  For
instance, if you add attributes to your screen objects and depend on them
being set up, an old screen will not bear them, and your application will
fail in mysterious ways.

By upgrading C<-version> each time such an incompatibility is introduced,
you let C<CGI::MxScreen> trap the error and produce an error message.

=back

=head2 Features

=over 4

=item C<internal_error> I<string>

Immediately abort current processing and emit the error message I<string>.
If a layout is defined, it is honoured during the generation of the
error message.

If you buffer STDOUT (which is the case by default), then all the output
currently generated will be discarded cleanly.  Otherwise, users might have
to scroll down to see the error message.

=item C<log>

Gives you access to the C<Log::Agent::Logger> logging object.  There is
always an object, whether or not you enabled logging, if only to redirect
all the logs to C</dev/null>.  This is the same object used by
C<CGI::MxScreen> to do its hardwired logging.

See L<Log::Agent::Logger> to learn what can be done with such objects.

=item C<play>

The entry point that dispatches the state machine handling.  Upon return,
the whole HTML has been generated and sent back to the browser.

=back

=head2 Utility Path

The concept of I<utility path> stems from the need to keep all callback
specification serializable.  Since C<Storable> cannot handle CODE references,
C<CGI::MxScreen> uses function names.  In some cases, we have a default
object to call the method on (e.g. during action callbacks), or one can
specify an object.  In some other case, a plain name must be used, and you
must tell C<CGI::MxScreen> in which packages it should look to find that name.

This is analogous to the PATH search done by the shell.  Unless you specify
an absolute path, the shell looks throughout your defined PATH directories,
stopping at the first match.

Here, we're looking through package namespaces.  For instance, given the
name "is_num", we could check C<main::is_num>, then C<Your::Module::is_num>,
etc...  That's what the utility path is.

The routine C<CGI::MxScreen::add_utils_path> must be used I<before> the
creation of the C<CGI::MxScreen> manager, and takes a list of strings,
which define the package namespaces to look through for field validation
callbacks and patching routines.  The reason it must be done I<before>
is that incoming CGI parameters are currently processed during the
manager's creation routine.

=head1 LOGGING

During its operation, C<CGI::MxScreen> can emit application logs.  The
amount emitted depends on the configuration, as described in
L<CGI::MxScreen::Config>.

Logs are emitted with the session number prefixed, for instance:

    (192.168.0.3-29592) t=0.13s usr=0.12s sys=0.01s [screen computation]

The logged session number is the IP address of the remote machine, and the
PID of the script when the session started.  It remains constant throughout
all the session.

There is also some timestamping and process pre-fixing done by the
underlying logging channel.  See L<Log::Agent::Stamping> for details.
The so-called "own" date stamping format is used by C<CGI::MxScreen>,
and it looks like this:

    01/04/18 12:08:22 script:

showing the date in yy/mm/dd format, and the time in HH::MM::SS format.
The C<script:> part is the process name, here the name of your CGI script.

At the "debug" logging level, you'll get this whole list of logs for
every intial script invocation:

    [main/0] t=0s u="ram" q="id=4"
    using "Mozilla/4.75 [en] (X11; U; Linux 2.4.3-ac4 i686)"
    t=0.20s usr=0.17s sys=0.01s [context restore + log init]
    t=1.15s usr=0.86s sys=0.05s [parameter init]
    t=1.71s usr=0.61s sys=0.07s [outside CGI::MxScreen]
    main()
    t=0.13s usr=0.12s sys=0.01s [screen computation]
    t=46.46s usr=43.42s sys=1.67s ["main" display]
    t=0.30s usr=0.29s sys=0.02s [context save]
    t=50.01s usr=45.53s sys=1.83s [total time] T=52.45s

The C<t=0s> indicates the start of a new session, and C<u="ram"> signals
that the request is made for an HTTP-authenticated user named I<ram>.
The C<[main/0]> indicates that we're in the state called I<main>, and C<0>
is the interaction counter (incremented at each roundtrip).
The C<q="id=4"> traces the query string.

The next line traces the user agent, and is only emitted at the start of
a new session.  May be useful if something goes wrong later on, so that
you can suspect the user's browser.

Then follows a bunch of timing lines, each indicating what was timed
in trailing square brackets.  The final total summs up all the other lines,
and also provides a precious C<T=52.45s> priece of statistics, measuring the
total wallclock time since the script startup.  This helps you evaluate
the overhead of loading the various modules.

The single C<main()> line traces the state information.  Here, since this
is the start of a new session, we enter the initial state and there's no
state transition.



( run in 1.072 second using v1.01-cache-2.11-cpan-cdf2f3d4e48 )