r31712@martha (orig r1247): groditi | 2009-10-02 17:02:01 -0400
[catagits/Reaction.git] / lib / Reaction / UI / Controller.pm
1 package Reaction::UI::Controller;
2
3 use Moose;
4 use Scalar::Util 'weaken';
5 use namespace::clean -except => [ qw(meta) ];
6
7 BEGIN { extends 'Catalyst::Controller'; }
8
9 has context => (is => 'ro', isa => 'Object', weak_ref => 1);
10 with(
11   'Catalyst::Component::InstancePerContext',
12   'Reaction::UI::Controller::Role::RedirectTo'
13 );
14
15 sub build_per_context_instance {
16   my ($self, $c, @args) = @_;
17   my $class = ref($self) || $self;
18   my $newself =  $class->new($self->_application, {%{$self || {}}, context => $c, @args});
19   return $newself;
20 }
21
22 sub push_viewport {
23   my $self = shift;
24   my $c = $self->context;
25   my $focus_stack = $c->stash->{focus_stack};
26   my ($class, @proto_args) = @_;
27   my %args;
28   if (my $vp_attr = $c->stack->[-1]->attributes->{ViewPort}) {
29     if (ref($vp_attr) eq 'ARRAY') {
30       $vp_attr = $vp_attr->[0];
31     }
32     if (ref($vp_attr) eq 'HASH') {
33       $class = $vp_attr->{class} if defined $vp_attr->{class};
34       %args = %{ $self->merge_config_hashes($vp_attr, {@proto_args}) };
35     } else {
36       $class = $vp_attr;
37       %args = @proto_args;
38     }
39   } else {
40     %args = @proto_args;
41   }
42
43   $args{ctx} = $c;
44
45   if (exists $args{next_action} && !ref($args{next_action})) {
46     $args{next_action} = [ $self, 'redirect_to', $args{next_action} ];
47   }
48   $focus_stack->push_viewport($class, %args);
49 }
50
51 sub pop_viewport {
52   return shift->context->stash->{focus_stack}->pop_viewport;
53 }
54
55 sub pop_viewports_to {
56   my ($self, $vp) = @_;
57   return $self->context->stash->{focus_stack}->pop_viewports_to($vp);
58 }
59
60 sub make_context_closure {
61   my($self, $closure) = @_;
62   my $ctx = $self->context;
63   weaken($ctx);
64   return sub { $closure->($ctx, @_) };
65 }
66
67 1;
68
69 __END__;
70
71 =head1 NAME
72
73 Reaction::UI::Controller - Reaction Base Controller Class
74
75 =head1 SYNOPSIS
76
77   package MyApp::Controller::Foo;
78   use strict;
79   use warnings;
80   use parent 'Reaction::UI::Controller';
81
82   use aliased 'Reaction::UI::ViewPort';
83
84   sub foo: Chained('/base') Args(0) {
85     my ($self, $ctx) = @_;
86
87     $ctx->push_viewport(ViewPort,
88       layout => 'foo',
89     );
90   }
91
92   1;
93
94 =head1 DESCRIPTION
95
96 Base Reaction Controller class, subclass of L<Catalyst::Controller>.
97
98 =head1 ROLES CONSUMED
99
100 =over 4
101
102 =item L<Catalyst::Component::InstancePerContext>
103
104 =item L<Reaction::UI::Controller::Role::RedirectTo>
105
106 Please not that this functionality is now deprecated.
107
108 =back
109
110 =head1 METHODS
111
112 =head2 push_viewport $vp_class, %args
113
114 Creates a new instance of the L<Reaction::UI::ViewPort> class
115 ($vp_class) using the rest of the arguments given (%args). Defaults of
116 the action can be overridden by using the C<ViewPort> key in the
117 controller configuration. For example to override the default number
118 of items in a CRUD list action:
119
120 __PACKAGE__->config(
121                     action => {
122                         list => { ViewPort => { per_page => 50 } },
123     }
124   );
125
126 The ViewPort is added to the L<Reaction::UI::Window>'s FocusStack in
127 the stash, and also returned to the calling code.
128
129 Related items:
130
131 =over
132
133 =item L<Reaction::UI::Controller::Root>
134 =item L<Reaction::UI::Window>
135
136 =back
137
138 TODO: explain how next_action as a scalar gets converted to the redirect arrayref thing
139
140 =head2 pop_viewport
141
142 =head2 pop_viewport_to $vp
143
144 Call L<Reaction::UI::FocusStack/pop_viewport> or
145 L<Reaction::UI::FocusStack/pop_viewport_to> on
146 the C<< $c->stash->{focus_stack} >>.
147
148 =head2 redirect_to $c, $to, $captures, $args, $attrs
149
150 Construct a URI and redirect to it.
151
152 $to can be:
153
154 =over
155
156 =item The name of an action in the current controller.
157
158 =item A L<Catalyst::Action> instance.
159
160 =item An arrayref of controller name and the name of an action in that
161 controller.
162
163 =back
164
165 $captures and $args default to the current requests $captures and
166 $args if not supplied.
167
168 =head2 make_context_closure
169
170 The purpose of this method is to prevent memory leaks.
171 It weakens the context object, often denoted $c, and passes it as the
172 first argument to the sub{} that is passed to the make_context_closure method.
173 In other words,
174
175 =over 4
176
177 make_context_closure returns sub { $sub_you_gave_it->($weak_c, @_)
178
179 =back
180
181 To further expound up this useful construct consider code written before
182 make_context_closure was created:
183
184     on_apply_callback =>
185         sub {
186           $self->after_search( $c, @_ );
187         }
188     ),
189
190 This could be rewritten as:
191
192     on_apply_callback => $self->make_context_closure(
193         sub {
194             my $weak_c = shift;
195             $self->after_search( $weak_c, @_ );
196         }
197     ),
198
199 Or even more succintly:
200
201     on_apply_callback => $self->make_context_closure(
202         sub {
203             $self->after_search( @_ );
204         }
205     ),
206
207 =head1 AUTHORS
208
209 See L<Reaction::Class> for authors.
210
211 =head1 LICENSE
212
213 See L<Reaction::Class> for the license.
214
215 =cut