CGI::MxScreen is a framework for building multi-screen stateful CGI programs. It is rather object-oriented, with some peculiarities brought by persistency constraints: all objects must be handled by Storable.

CGI::MxScreen is based on the CGI module, and co-operates with it, meaning you are able to use most CGI calls normally. The few places where you should not is where CGI::MxScreen supersedes the CGI functionalities: for instance, there's no need to propagate hidden values when you use CGI::MxScreen.

CGI::MxScreen is architected around the concept of screens. Among the set of defined screens within the same script, only one is visible at a time. One moves around the various screens by pressing buttons, which submit data to the server and possibly move you to a different screen. The state machine is handled by CGI::MxScreen, the user only defines which state (screen) a button shall move the application to

CGI::MxScreen is stateful in the sense that many of the runtime objects created to operate (and screens are among those) are made persistent. This is a very interesting property, because you do not have to worry too much about the underlying stateless nature of the CGI protocol. The CGI module brought the statefulness to the level of form controls, but CGI::MxScreen raises it to the level of the application itself.

CGI::MxScreen is not meant to be used for so-called quick and dirty scripts, or for scripts which do not require some fair amount of round trips between the browser and the server. You'll be better off with using the good old CGI module. However, for more complex web applications, where there is a fair amount of processing required on the server side, and where each script involves several states, CGI::MxScreen is for you.

The module is a superset of the CGI module. You can continue to use CGI routines wherever you like.

It handles sessions for you, saving much of the application state, and making CGI hidden parameters useless. You may save sessions within the browser, or to files, or you may even build your own medium backend. You may also define your own serializing options, although Storable is natively supported. See CGI::MxScreen::Session::Medium for the medium interface and CGI::MxScreen::Serializer for the serialization interface.

It handles the state machine for you. You define the various screen objects, and then specify, for each submit button, which state the application should go. The target state can be specified statically, or computed dynamically by the application. Action routines can be attached to the button, to run some processing during the state change. See CGI::MxScreen::Form::Button for more information.

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

Any script output done before the screen's 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.

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 CGI::MxScreen::Form::Field for more information.

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).

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

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 CGI::MxScreen::Config for details.

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

CGI::MxScreen uses 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.

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

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.

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.

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.

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 display routine. Any processing action attached to the button is also run at that point.

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

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

The enclosing form setting is emitted, and the screen's 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 display is trapped and cleanly logged, before displaying an internal error message.

The application context is saved, the form is closed, and buffered output is emitted. A final log tracing the total time spent is emitted.

The following example demonstrates the various common operations that need to be performed with CGI::MxScreen.

An important comment first: if we forget about the fact that you need an object per screen (which has some code overhead compared to using plain CGI), you will need to write more declarative code with CGI::MxScreen than you would with CGI, but this buys you more persistent state for fields, and lets you define the state transitions and associated processing for buttons.

Moreover, please note that this example could be written in less code by using the CGI module only. But CGI::MxScreen is not aimed at simple scripts.

Our example defines a two-state script, where one choose a color in the first screen, and then a week day in the second screen. The script reminds you about the choice made in the other screen, if any. It is possible to "redraw" the first screen to prove that the selection made is sticky. First, the whole script:

This defines the first state, Color. It inherits from CGI::MxScreen::Screen, as it should.

5 use CGI qw/:standard/;
6

We're going to use CGI routines. We could do with less than what is exported by the :standard tag, but I did not bothered.

7 sub init {
8 my $self = shift;
9 $self->vars->{color} = "";
10 }
11

The init() routine is called on the screen the first time it is created. Upon further invocations, the same screen object will be used and re-used each time we need to access the Color state.

To differentiate from a plain CGI script which would use hidden parameters to propagate the information, we store the application variable in the persistent hash table, which every screen can access through $self->vars. Here, we initialize the "color" key, because any access to an unknown key is an error at runtime (to avoid malicious typos).

12 sub display {
13 my $self = shift;

The display() routine is invoked by the state manager on the screen selected for displaying.

14 print h1($self->screen_title);
15

Prints screen title. This refers to the defined title in the manager, which are declared for each known screen further down on lines 78-82.

This declaration is very important. It tells CGI::MxScreen that the screen makes use of a field named "color", and whose value should be stored in the global persistent hash under the key "color" (as per the -storage indication).

The remaining attributes are simply collected to be passed to the popup_menu() routine via $color-properties> below. They could be omitted, and added inline when popup_menu() is called, but it's best to regroup common things together.

The underlying object created by record_field() will be serialized and included in the CGI::MxScreen context (only the relevant attributes are serialized, i.e. CGI parameters such as -values are not). This will allow the processing engine to honour some meaningful actions, such as validation, storage, or on-the-fly patching.

Another important property of those objects is that CGI::MxScreen will update the value attribute, which would be noticeable if there was no -default line: you could query $color-value> to get the current CGI parameter value, as submitted.

If we have been in the Weekday screen, then the key "weekday" will be existing in the global hash $self->vars, because it is created by the init() routine of that object, at line 46. If we tried to access the key without protecting by the exists test on line 25, we'd get a fatal error saying:

access to unknown key 'weekday'

This protection can be disabled if you want it so, but it is on by default. It will probably save you one day, but unfortunately this is a runtime check.

The above is generating the sole input of this screen, i.e. a popup menu so that you can select your favorite color. Note that we're passing popup_menu(), which is a routine from the CGI module, a list of arguments derived from the recorded field $color, created at line 16.

The Redraw button simply redisplays the current screen, i.e. there is no transition to another screen (state). The current_screen routine returns the name of the current screen we're in, along with all the parameters we were called with, so that the transition is indeed towards the exact same state.

We're finishing the display routine by calling the submit() routine from the CGI module to generate the submit buttons. Here again, we're calling properties() on each button object to expand the CGI parameters, just like we did for the field on line 27.

Recall that init() is called when the screen is created. Since screen objects are made persistent for the duration of the whole session (i.e. while the user is interacting with the script's forms), that means the routine is called once for every screen that gets created.

Here, we initialize the "weekday" key, which is necessary because we're going to use it line 58 below...

We remind them about the color they have chosen in the previous screen. Note that we don't rely on a hidden parameter to propagate that value: because it is held in the global persistent hash, it gets part of the session context and is there for the duration of the session.

The declaration of the field used to ask them about their preferred week day. It looks a lot like the one we did for the color, on lines 16-22, with the exception that the field name is "day" but the storage in the context is "weekday" (we used the same string "color" previously).

The above line generates the popup. This will create a selection list whose CGI name is "day". However, upon reception of that parameter, CGI::MxScreen will immediately save the value to the location identified by the -storage line, thereby making the value available to the application via the $self->vars hash.

We declare a button named Back, which will bring us back to the screen we were when we sprang into the current screen. That's what 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 redraw button like we had in the Color screen, which would make a transition to the same state, then spring_screen will still correctly point to Color, whereas previous_screen would be Weekday in that case.

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

This closes the 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 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.

The states of our state machine are described above. The keys of the -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 $self->title, but there's no obligation for screens to display that information. However, the manager needs to know because when the 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.

To learn about the interface of the CGI::MxScreen manager object, see "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 CGI::MxScreen::Screen.

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

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

Do not forget that 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 Storable by default, well, actually CGI::MxScreen::Serializer::Storable, which is wrapping the Storable interface to something CGI::MxScreen understands). See CGI::MxScreen::Config to learn how to change the serializer, and CGI::MxScreen::Serializer for the interface it must follow.

A state is a screen name plus all the arguments that are given to its 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:

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

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

["Color", "blue"]

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

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

As a scalar name, e.g. 'validate'.

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

As a list reference, starting with a scalar name:

['routine', @args]

This specifies that routine(@args) should be called on the screen object.

As a list reference, beginning with an object reference:

[$obj, 'routine', @args]

which specifies that <$obj->routine(@args)> should be called, i.e. the target object is no longer the screen object. It is available to CGI::MxScreen::Form::Button objects only.

The public interface with the manager object is quite limited. The main entry points are the creation routine, which configures the overall operating mode, and the play() routine, which launches the state machine resolution.

Optional, sets the default background color to be used for all screens. If unspecified, the value is gray75, aka "#bfbfbf", which is the default background in Netscape on Unix. The value you supply will be used in the BGCOLOR HTML tag, so any legal value there can be used. For instance:

Mandatory, defines the list of valid states, whose class will handle it, and what the title of the page should be in that state. Usually, there is identity between a screen and a state, but via the display() parameters, you can have the same screen object used in two different states, with a slightly different mode of operation.

The hash reference given here is indexed by state names. The values must be array references, and their content is the list of arguments to supply to the screen's creation routine, plus a -class argument defining the class to use. See "Creation Routine" in CGI::MxScreen::Screen.

Optional, defines a session timeout, which will be enforced by 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.

Defines the script's version. This is your versioning scheme, which has nothing to do with the one used by 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 -version each time such an incompatibility is introduced, you let CGI::MxScreen trap the error and produce an error message.

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

The concept of utility path stems from the need to keep all callback specification serializable. Since Storable cannot handle CODE references, 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 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 main::is_num, then Your::Module::is_num, etc... That's what the utility path is.

The routine CGI::MxScreen::add_utils_path must be used before the creation of the 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 before is that incoming CGI parameters are currently processed during the manager's creation routine.

During its operation, CGI::MxScreen can emit application logs. The amount emitted depends on the configuration, as described in 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 Log::Agent::Stamping for details. The so-called "own" date stamping format is used by 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 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:

The t=0s indicates the start of a new session, and u="ram" signals that the request is made for an HTTP-authenticated user named ram. The [main/0] indicates that we're in the state called main, and 0 is the interaction counter (incremented at each roundtrip). The 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 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 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.

Note the very large time spent by the display() routine for that screen. This is because Carp::Datum was on, and there was a lot of activity to trace.

Compare this to the following log, where the user pressed a button called refresh, which simply re-displays the same screen, and where Carp::Datum was turned off:

The new d=19s item on the first line indicates the elapsed time since the end of the first invocation of the script, and this new one. It is the time the user contemplated the screen before pressing a button.

Note that there is no q="id=4" shown: CGI::MxScreen uses POST requests between its invocations, and does not propagate the initial query string. It is up to you to save any relevant information into the context.

The following table indicates the logging level used to emit each of the logging lines outlined above:

All timing logs but the last one summarizing the total time are made at the debug level. All state transitions (button press, or even bounce exceptions) are logged at the notice level. Invocations are logged at the warning level, in order to trace them more systematically.

CGI::MxScreen began when Raphael Manfredi, who knew next to nothing about CGI programming, stumbled on the wonderful MxScreen program, by Tom Christiansen, circa 1998. It was a graphical query compiler for his Magic: The Gathering database. I confess I learned eveything there was to learn about by studying this program. I owed so much to that MxScreen script that I decided to keep the name in the module.

However, MxScreen was a single application, very well written, but not reusable without doing massive cut-and-paste, and rather monolithic. The first CGI::MxScreen version was written by Raphael Manfredi to modularize the various concepts in late 1998 and early 1999. It was never published, and was too procedural.

In late 1999, I introduced my CGI::MxScreen to Christophe Dehaudt. After studying it for a while, he bought the overall concept, but proposed to drop the procedural approach and switch to a pure object-oriented design, to make the framework easier to work with. I agreed.

The current version of CGI::MxScreen is the result of a joint work between us. Christophe did the initial experimenting with the new ideas, and Raphael consolidated the work, then wrote the whole documentation and regression test suite. We discussed the various implementation decisions together, and although the result is necessarily a compromise, I (Raphael) believe it is a good compromise.

We managed to use CGI::MxScreen in the industrial development of a web-based project time tracking system. The source was well over 20000 lines of pure Perl code (comments and blank lines stripped), and we reused more than 50000 lines of CPAN code. I don't think we would have succeeded without CGI::MxScreen, and without CPAN.

The public release of CGI::MxScreen was delayed more than a year because the dependencies of the module needed to be released first, and also we were lacking CGI::Test which was developped only recently. Without it, writing the regression test suite of CGI::MxScreen would have been a real pain, due to its context-sensitive nature. See CGI::Test if you're curious.