1package CGI::Application::Plugin::Forward;
2
3
4use warnings;
5use strict;
6use Carp;
7use vars qw(@ISA @EXPORT);
8@ISA = ('Exporter');
9
10@EXPORT = ('forward');
11
12=head1 NAME
13
14CGI::Application::Plugin::Forward - Pass control from one run mode to another
15
16=head1 VERSION
17
18Version 1.06
19
20=cut
21
22our $VERSION = '1.06';
23
24use CGI::Application;
25if (CGI::Application->can('new_hook')) {
26    CGI::Application->new_hook('forward_prerun');
27}
28
29=head1 SYNOPSIS
30
31    use base 'CGI::Application';
32    use CGI::Application::Plugin::Forward;
33
34    sub setup {
35        my $self = shift;
36        $self->run_modes([qw(
37            start
38            second_runmode
39        )]);
40    }
41    sub start {
42        my $self = shift;
43        return $self->forward('second_runmode');
44    }
45    sub second_runmode {
46        my $self = shift;
47
48        my $rm = $self->get_current_runmode;  # 'second_runmode'
49
50    }
51
52=head1 DESCRIPTION
53
54The forward method passes control to another run mode and returns its
55output.  This is equivalent to calling C<< $self->$other_runmode >>,
56except that L<CGI::Application>'s internal value of the current run mode
57is updated.
58
59This means that calling C<< $self->get_current_runmode >> after calling
60C<forward> will return the name of the new run mode.  This is useful for
61modules that depend on the name of the current run mode such as
62L<CGI::Application::Plugin::AnyTemplate>.
63
64For example, here's how to pass control to a run mode named C<other_action>
65from C<start> while updating the value of C<current_run_mode>:
66
67    sub setup {
68        my $self = shift;
69        $self->run_modes({
70            start         => 'start',
71            other_action  => 'other_method',
72        });
73    }
74    sub start {
75        my $self = shift;
76        return $self->forward('other_action');
77    }
78    sub other_method {
79        my $self = shift;
80
81        my $rm = $self->get_current_runmode;  # 'other_action'
82    }
83
84Note that forward accepts the I<name> of the run mode (in this case
85I<'other_action'>), which might not be the same as the name of the
86method that handles the run mode (in this case I<'other_method'>)
87
88
89You can still call C<< $self->other_method >> directly, but
90C<current_run_mode> will not be updated:
91
92    sub setup {
93        my $self = shift;
94        $self->run_modes({
95            start         => 'start',
96            other_action  => 'other_method',
97        });
98    }
99    sub start {
100        my $self = shift;
101        return $self->other_method;
102    }
103    sub other_method {
104        my $self = shift;
105
106        my $rm = $self->get_current_runmode;  # 'start'
107    }
108
109
110Forward will work with coderef-based runmodes as well:
111
112    sub setup {
113        my $self = shift;
114        $self->run_modes({
115            start         => 'start',
116            anon_action   => sub {
117                my $self = shift;
118                my $rm = $self->get_current_runmode;  # 'anon_action'
119            },
120        });
121    }
122    sub start {
123        my $self = shift;
124        return $self->forward('anon_action');
125    }
126
127=head1 FORWARD vs. REDIRECT
128
129Calling C<forward> changes the run mode of your application, but it
130stays within the same HTTP request.
131
132To redirect to a new runmode using a completely new web request, you
133might consider using the C<redirect> method provided by
134L<CGI::Application::Plugin::Redirect>.
135
136The advantage of using an external redirect as opposed to an internal
137forward is that it provides a 'clean break' between pages.
138
139For instance, in a typical BREAD application (Browse, Read, Edit, Add,
140Delete), after the user completes an action, you usually return the user
141to the Browse list.  For instance, when the user adds a new record
142via a POST form, and your app returns them to the list of records.
143
144If you use C<forward>, then you are still in the same request as the
145original I<add record>.  The user might hit I<reload>, expecting to
146refresh the list of records.  But in fact, I<reload> will attempt to
147repost the I<add record> form.  The user's browser might present a
148warning about reposting the same data.  The browser may refuse to
149redisplay the page, due for caching reasons.
150
151So in this case, it may make more sense to do a fresh HTTP redirect back
152to the Browse list.
153
154=head1 METHODS
155
156=head2 forward
157
158Runs another run mode passing any parameters you supply.  Returns the
159output of the new run mode.
160
161    return $self->forward('run_mode_name', @run_mode_params);
162
163=cut
164
165sub forward {
166    my $self     = shift;
167    my $run_mode = shift;
168
169    if ($CGI::Application::Plugin::AutoRunmode::VERSION) {
170        if (CGI::Application::Plugin::AutoRunmode->can('is_auto_runmode')) {
171            if (CGI::Application::Plugin::AutoRunmode::is_auto_runmode($self, $run_mode)) {
172                $self->run_modes( $run_mode => $run_mode);
173            }
174        }
175    }
176
177    my %rm_map = $self->run_modes;
178    if (not exists $rm_map{$run_mode}) {
179        croak "CAP::Forward: run mode $run_mode does not exist";
180    }
181    my $method = $rm_map{$run_mode};
182
183    if ($self->can($method) or ref $method eq 'CODE') {
184        $self->{__CURRENT_RUNMODE} = $run_mode;
185        if ($self->can('call_hook')) {
186            $self->call_hook('forward_prerun');
187        }
188        return $self->$method(@_);
189    }
190    else {
191        croak "CAP::Forward: target method $method of run mode $run_mode does not exist";
192    }
193}
194
195
196=head1 HOOKS
197
198Before the forwarded run mode is called, the C<forward_prerun> hook is
199called. You can use this hook to do any prep work that you want to do
200before any new run mode gains control.
201
202This is similar to L<CGI::Application>'s built in C<cgiapp_prerun>
203method, but it is called each time you call L<forward>; not just the
204when your application starts.
205
206    sub setup {
207        my $self = shift;
208        $self->add_callback('forward_prerun' => \&prepare_rm_stuff);
209    }
210
211    sub prepare_rm_stuff {
212        my $self = shift;
213        # do any necessary prep work here....
214    }
215
216Note that your hooked method will only be called when you call
217L<forward>.  If you never call C<forward>, the hook will not be called.
218In particuar, the hook will not be called for your application's
219C<start_mode>.  For that, you still use C<cgiapp_prerun>.
220
221If you want to have a method run for every run mode I<including> the
222C<start_mode>, then you can call the hook directly from
223C<cgiapp_prerun>.
224
225    sub setup {
226        my $self = shift;
227        $self->add_callback('forward_prerun' => \&prepare_rm_stuff);
228    }
229    sub cgiapp_prerun {
230        my $self = shift;
231        $self->prepare_rm_stuff;
232    }
233
234    sub prepare_rm_stuff {
235        my $self = shift;
236        # do any necessary prep work here....
237    }
238
239Alternately, you can hook C<cgiapp_prerun> to the C<forward_prerun>
240hook:
241
242    sub setup {
243        my $self = shift;
244        $self->add_callback('forward_prerun' => \&cgiapp_prerun);
245    }
246    sub cgiapp_prerun {
247        my $self = shift;
248        # do any necessary prep work here....
249    }
250
251This is a less flexible solution, since certain things that can be done
252in C<cgiapp_prerun> (like setting C<prerun_mode>) won't work when the
253method is called from the C<forward_prerun> hook.
254
255=head1 AUTHOR
256
257Michael Graham, C<< <mag-perl@occamstoothbrush.com> >>
258
259=head1 BUGS
260
261Please report any bugs or feature requests to
262C<bug-cgi-application-plugin-forward@rt.cpan.org>, or through the web interface at
263L<http://rt.cpan.org>.  I will be notified, and then you'll automatically
264be notified of progress on your bug as I make changes.
265
266=head1 ACKNOWLEDGEMENTS
267
268Thanks to Mark Stosberg for the idea and...well...the implementation as
269well.
270
271=head1 COPYRIGHT & LICENSE
272
273Copyright 2005 Michael Graham, All Rights Reserved.
274
275This program is free software; you can redistribute it and/or modify it
276under the same terms as Perl itself.
277
278=cut
279
2801; # End of CGI::Application::Plugin::Forward