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