CGI-MxScreen
view release on metacpan or search on metacpan
MxScreen.pm view on Meta::CPAN
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.
=item *
The application context is saved, the form is closed, and buffered output
is emitted. A final log tracing the total time spent is emitted.
=back
=head2 Example
The following example demonstrates the various common operations that need
to be performed with C<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 C<CGI>), you will need to write more I<declarative> code with
C<CGI::MxScreen> than you would with C<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 C<CGI> module only. But C<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:
1 #!/usr/local/bin/perl -T
2
3 package Color; use base qw(CGI::MxScreen::Screen);
4
5 use CGI qw/:standard/;
6
7 sub init {
8 my $self = shift;
9 $self->vars->{color} = "";
10 }
11
12 sub display {
13 my $self = shift;
14 print h1($self->screen_title);
15
16 my $color = $self->record_field(
17 -name => "color",
18 -storage => "color",
19 -default => $self->vars->{color} || "Green",
20 -override => 1,
21 -values => [qw(Red Green Blue White Black Yellow Orange Cyan)],
22 );
23
24 print p("You told me your favorite weekday was", $self->vars->{weekday})
25 if exists $self->vars->{weekday};
26
27 print p("Your favorite color is", popup_menu($color->properties));
28
29 my $ok = $self->record_button(
30 -name => "Next",
31 -target => "Weekday");
32
33 my $redraw = $self->record_button(
34 -name => "Redraw",
35 -target => $self->current_screen);
36
37 print submit($ok->properties), submit($redraw->properties);
38 }
39
40 package Weekday; use base qw(CGI::MxScreen::Screen);
41
42 use CGI qw/:standard/;
43
44 sub init {
45 my $self = shift;
46 $self->vars->{weekday} = "";
47 }
48
49 sub display {
50 my $self = shift;
51 print h1($self->screen_title);
52
53 print p("You told me your favorite color was", $self->vars->{color});
54
55 my $weekday = $self->record_field(
56 -name => "day",
57 -storage => "weekday",
58 -default => $self->vars->{weekday} || "Mon",
59 -override => 1,
60 -values => [qw(Mon Tue Wed Thu Fri Sat Sun)],
61 );
62
63 print p("Your favorite weekday is", popup_menu($weekday->properties));
64
65 my $back = $self->record_button(
66 -name => "Back",
67 -target => $self->spring_screen,
68 );
69
70 print submit($back->properties);
71 }
72
73 package main;
74
75 require CGI::MxScreen;
76
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
86 $manager->play();
87
Let's study this a piece at a time:
1 #!/usr/local/bin/perl -T
2
The classical declaration for a CGI script, in taint mode.
3 package Color; use base qw(CGI::MxScreen::Screen);
4
This defines the first state, C<Color>. It inherits from
C<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 C<:standard> tag, but I did not bothered.
7 sub init {
8 my $self = shift;
9 $self->vars->{color} = "";
10 }
11
The C<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 C<Color> state.
To differentiate from a plain C<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 C<$self-E<gt>vars>.
Here, we initialize the C<"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 C<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.
16 my $color = $self->record_field(
17 -name => "color",
18 -storage => "color",
19 -default => $self->vars->{color} || "Green",
20 -override => 1,
21 -values => [qw(Red Green Blue White Black Yellow Orange Cyan)],
22 );
23
This declaration is very important. It tells C<CGI::MxScreen> that the
screen makes use of a field named C<"color">, and whose value should be
stored in the global persistent hash under the key C<"color"> (as per
the C<-storage> indication).
The remaining attributes are simply collected to be passed to the
C<popup_menu()> routine via C<$color->properties> below. They could be
omitted, and added inline when C<popup_menu()> is called, but it's best
to regroup common things together.
The underlying object created by C<record_field()> will be serialized
and included in the C<CGI::MxScreen> context (only the relevant attributes
are serialized, i.e. C<CGI> parameters such as C<-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 C<CGI::MxScreen> will
update the value attribute, which would be noticeable if there was no
C<-default> line: you could query C<$color->value> to get the current
CGI parameter value, as submitted.
24 print p("You told me your favorite weekday was", $self->vars->{weekday})
25 if exists $self->vars->{weekday};
26
If we have been in the C<Weekday> screen, then the key C<"weekday"> will
be existing in the global hash C<$self-E<gt>vars>, because it is created by
the C<init()> routine of that object, at line 46. If we tried to access
the key without protecting by the C<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.
27 print p("Your favorite color is", popup_menu($color->properties));
28
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
C<popup_menu()>, which is a routine from the C<CGI> module, a list of
arguments derived from the recorded field C<$color>, created at line 16.
29 my $ok = $self->record_button(
30 -name => "Next",
31 -target => "Weekday");
32
This declaration is also very important. We're using C<record_button()>
to declare a state transition: we wish to move to the C<Weekday> screen
when the button I<Next> is pressed.
33 my $redraw = $self->record_button(
34 -name => "Redraw",
35 -target => $self->current_screen);
36
The I<Redraw> button simply redisplays the current screen, i.e. there
is no transition to another screen (state). The I<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.
37 print submit($ok->properties), submit($redraw->properties);
38 }
39
We're finishing the C<display> routine by calling the C<submit()> routine
from the C<CGI> module to generate the submit buttons. Here again, we're
calling C<properties()> on each button object to expand the CGI parameters,
just like we did for the field on line 27.
40 package Weekday; use base qw(CGI::MxScreen::Screen);
41
42 use CGI qw/:standard/;
43
This defines the second state, C<Weekday>. It inherits from
C<CGI::MxScreen::Screen>, as it should. We also import the C<CGI>
functions in that new package.
Note that the name of the class need not be the name of the state.
The association between state name and classes is done during the creation
of the manager object (see lines 78-82).
44 sub init {
45 my $self = shift;
46 $self->vars->{weekday} = "";
47 }
48
Recall that C<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 I<once> for every screen that gets created.
Here, we initialize the C<"weekday"> key, which is necessary because we're
going to use it line 58 below...
49 sub display {
50 my $self = shift;
51 print h1($self->screen_title);
52
This is the C<display()> routine for the screen C<Weekday>. It will be
called by the C<CGI::MxScreen> manager when the selected state is C<"Weekday">
(name determined line 81 below).
53 print p("You told me your favorite color was", $self->vars->{color});
54
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.
55 my $weekday = $self->record_field(
56 -name => "day",
57 -storage => "weekday",
58 -default => $self->vars->{weekday} || "Mon",
59 -override => 1,
60 -values => [qw(Mon Tue Wed Thu Fri Sat Sun)],
61 );
62
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 C<"day"> but the storage in the context
is C<"weekday"> (we used the same string C<"color"> previously).
63 print p("Your favorite weekday is", popup_menu($weekday->properties));
64
The above line generates the popup. This will create a selection list
whose CGI name is C<"day">. However, upon reception of that parameter,
C<CGI::MxScreen> will immediately save the value to the location identified
by the C<-storage> line, thereby making the value available to the application
via the C<$self-E<gt>vars> hash.
65 my $back = $self->record_button(
66 -name => "Back",
67 -target => $self->spring_screen,
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.
( run in 0.727 second using v1.01-cache-2.11-cpan-364913b4093 )