File Coverage

blib/lib/Catalyst/View/BasePerRequest.pm
Criterion Covered Total %
statement 128 522 24.5
branch 39 206 18.9
condition 9 24 37.5
subroutine 33 185 17.8
pod 10 176 5.6
total 219 1113 19.6


line stmt bran cond sub pod time code
1              
2             our $VERSION = '0.008';
3             our $DEFAULT_FACTORY = 'Catalyst::View::BasePerRequest::Factory';
4              
5             use Moose;
6 1     1   1750886 use HTTP::Status ();
  1         2  
  1         5  
7 1     1   5310 use Scalar::Util ();
  1         2  
  1         24  
8 1     1   6 use Module::Runtime ();
  1         2  
  1         13  
9 1     1   5  
  1         1  
  1         559  
10             extends 'Catalyst::View';
11              
12             has 'catalyst_component_name' => (is=>'ro');
13             has 'app' => (is=>'ro');
14             has 'ctx' => (is=>'ro');
15             has 'root' => (is=>'rw', required=>1, default=>sub { shift });
16             has 'parent' => (is=>'rw', predicate=>'has_parent');
17             has 'content_type' => (is=>'ro', required=>0, predicate=>'has_content_type');
18             has 'code' => (is=>'rw', predicate=>'has_code');
19             has 'status_codes' => (is=>'rw', predicate=>'has_status_codes');
20             has 'injected_views' => (is=>'rw', predicate=>'has_injected_views');
21             has 'forwarded_args' => (is=>'rw', predicate=>'has_forwarded_args');
22              
23             my ($app, %views) = @_;
24             $app->config->{views} = \%views;
25 0     0 0 0 }
26 0         0  
27             my ($app, $method, @args) = @_;
28             if(scalar(@args) > 1) {
29             $app->config->{views}{$method} = \@args;
30 0     0 0 0 } else {
31 0 0       0 $app->config->{views}{$method} = $args[0];
32 0         0 }
33             }
34 0         0  
35             my ($app, $ct) = @_;
36             $app->config->{content_type} = $ct;
37             }
38              
39 0     0 0 0 my $app = shift;
40 0         0 my $codes = ref($_[0]) ? shift : [shift];
41             $app->config->{status_codes} = $codes;
42             }
43              
44 0     0 0 0 my ($class, $app, $args) = @_;
45 0 0       0 my $merged_args = $class->merge_config_hashes($class->config, $args);
46 0         0  
47             $merged_args = $class->modify_init_args($app, $merged_args) if $class->can('modify_init_args');
48             my %status_codes = $class->inject_http_status_helpers($merged_args);
49             $merged_args->{status_codes} = \%status_codes if scalar(keys(%status_codes));
50 2     2 1 64437 my @injected_views = $class->inject_view_helpers($merged_args);
51 2         8 $merged_args->{injected_views} = \@injected_views if scalar @injected_views;
52              
53 2 50       207 my $factory_class = Module::Runtime::use_module($class->factory_class($app, $merged_args));
54 2         13 return my $factory = $class->build_factory($factory_class, $app, $merged_args);
55 2 100       9 }
56 2         20  
57 2 50       5 my ($class, $merged_args) = @_;
58             if(my $views = $merged_args->{views}) {
59 2         10 require Sub::Util;
60 2         60 foreach my $method (keys %$views) {
61             my ($view_name, @args_proto) = ();
62             my $options_proto = $views->{$method};
63              
64 2     2 0 6 my $global_args_generator;
65 2 50       13 if( (ref($options_proto)||'') eq 'ARRAY') {
66 0         0 ($view_name, @args_proto) = @$options_proto;
67 0         0 $global_args_generator = (ref($args_proto[0])||'') eq 'CODE' ?
68 0         0 shift(@args_proto) :
69 0         0 sub { @args_proto };
70             } else {
71 0         0 $view_name = $options_proto;
72 0 0 0     0 }
73 0         0  
74             no strict 'refs';
75             *{"${class}::${method}"} = Sub::Util::set_subname "${class}::${method}" => sub {
76 0 0 0 0   0 my ($self, @args) = @_;
  0         0  
77             my @global_args = $global_args_generator ? $global_args_generator->($self, $self->ctx, @args) : ();
78 0         0 my $view = $self->ctx->view($view_name, @global_args, @args);
79              
80             $view->root($self->root);
81 1     1   7 $view->parent($self);
  1         1  
  1         1813  
82 0         0  
83 0     0   0 return $view;
84 0 0       0 };
85 0         0 }
86             return keys %$views;
87 0         0 }
88 0         0 return ();
89             }
90 0         0  
91 0         0 my ($class, $merged_args) = @_;
92              
93 0         0 my %status_codes = ();
94             if(exists $merged_args->{status_codes}) {
95 2         5 %status_codes = map { $_=>1 } @{$merged_args->{status_codes}};
96             }
97              
98             foreach my $helper( grep { $_=~/^http/i} @HTTP::Status::EXPORT_OK) {
99 2     2 0 7 my $subname = lc $helper;
100             my $code = HTTP::Status->$helper;
101 2         4 if(scalar(keys(%status_codes))) {
102 2 100       7 next unless $status_codes{$code};
103 1         3 }
  3         9  
  1         4  
104             eval "sub ${\$class}::${\$subname} { return shift->respond(HTTP::Status::$helper,\\\@_) }";
105             eval "sub ${\$class}::set_${\$subname} {
106 2         8 my (\$self, \@headers) = \@_;
  144         257  
107 138         347 \$self->ctx->res->status(HTTP::Status::$helper);
108 138         438 \$self->ctx->res->headers->push_header(\@headers) if \@headers;
109 138 100       265 return \$self;
110 69 100       120 }";
111             }
112 72     0 0 99  
  72     0 0 163  
  72     0 0 3456  
  0     0 0 0  
  0     1 0 0  
  0     0 0 0  
  0     0 0 0  
  1     0 0 473  
  0     0 0 0  
  0     1 0 0  
  0     0 0 0  
  0     0 0 0  
  1     0 0 453  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0     0 0 0  
  0         0  
  0         0  
  0         0  
113 72 0   0 0 178 return %status_codes;
  72 0   0 0 161  
  72 0   0 0 5130  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 50   1 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  1 50   1 0 487  
  1 0   0 0 27  
  1 0   0 0 177  
  1 0   0 0 190  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0 0   0 0 0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  1         463  
  1         27  
  1         126  
  1         7  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
  0         0  
114             }
115              
116             my ($class, $app, $merged_args) = @_;
117             return $DEFAULT_FACTORY;
118             }
119              
120             my ($class, %args) = @_;
121 2         15 return $class->new(%args);
122             }
123              
124             my ($class, $factory_class, $app, $merged_args) = @_;
125 2     2 0 6 return my $factory = bless +{
126 2         11 class=>$class,
127             app=>$app,
128             merged_args=>$merged_args,
129             }, $factory_class;
130 12     12 1 35 }
131 12         358  
132             my ($self, $ctx, @args) = @_;
133             die "You need to write a 'render' method!";
134             }
135 2     2 0 13  
136 2         14 my ($self, $c, @args) = @_;
137             $self->forwarded_args(\@args);
138             return $self->respond();
139             }
140              
141             my ($self, $status, $headers, @args) = @_;
142             for my $r ($self->ctx->res) {
143             $r->status($status) if $status && $r->status; # != 200; # Catalyst sets 200
144 0     0 0 0  
145 0         0 Module::Runtime::use_module('Catalyst::View::BasePerRequest::Exception::InvalidStatusCode')->throw(status_code=>$r->status)
146             if $self->has_status_codes && !$self->status_codes->{$r->status};
147            
148             $r->content_type($self->content_type) if !$r->content_type && $self->has_content_type;
149 1     1 1 183 $r->headers->push_header(@{$headers}) if $headers;
150 1         47 $r->body($self->get_rendered(@args));
151 1         13 }
152             return $self; # allow chaining
153             }
154              
155 3     3 1 11 my $self = shift;
156 3         79 my @rendered = ();
157 3 100 66     84  
158             eval {
159             my @args = $self->prepare_render_args(@_);
160 3 50 33     417 @rendered = map {
161             Scalar::Util::blessed($_) && $_->can('get_rendered') ? $_->get_rendered : $_;
162 3 50 33     248 } $self->render(@args);
163 3 100       498 1;
  2         157  
164 3         36 } || do {
165             $self->do_handle_render_exception($@);
166 3         35 };
167              
168             return $self->flatten_rendered_for_response_body(@rendered);
169             }
170 12     12 0 1106  
171 12         21  
172              
173             my ($self, $err) = @_;
174 12         31 return $err->rethrow if Scalar::Util::blessed($err) && $err->can('rethrow');
175             my $class = Module::Runtime::use_module($self->render_error_class);
176 12 100 66     45 $class->throw(render_error=>$err);
  12         402  
177             }
178 12         40  
179 12 50       23 my ($self, @args) = @_;
180 0         0  
181             if($self->has_code) {
182             my $inner = $self->render_code;
183 12         31 unshift @args, $inner; # pass any $inner as an argument to ->render()
184             }
185              
186 12     12 0 34 return ($self->ctx, @args);
187             }
188 0     0 0 0  
189             my $self = shift;
190             my @inner = map {
191 0     0 0 0 Scalar::Util::blessed($_) && $_->can('get_rendered') ? $_->get_rendered : $_;
192 0 0 0     0 } $self->execute_code_callback($self->prepare_render_code_args);
193 0         0  
194 0         0 my $flat = $self->flatten_rendered_for_inner_content(@inner);
195             return $flat;
196             }
197              
198 12     12 0 28 my ($self, @args) = @_;
199             return $self->code->(@args);
200 12 100       387 }
201 6         44  
202 6         20 my ($self) = @_;
203             return $self;
204             }
205 12         320  
206              
207             my $self = shift;
208             return join '', grep { defined($_) } @_;
209 6     6 0 10 }
210              
211 6 50 33     35 my ($self, $name, $options) = @_;
  6         231  
212             my %options = $options ? %$options : ();
213             my $default = exists($options{default}) ? $options{default} : '';
214 6         20  
215 6         14 return exists($self->ctx->stash->{view_blocks}{$name}) ?
216             $self->ctx->stash->{view_blocks}{$name} :
217             $default;
218             }
219 6     6 0 15  
220 6         162 my $self = shift;
221             if((ref($_[0])||'') eq 'CODE') {
222             return $self->flatten_rendered_for_content_blocks($_[0]->($_[1]));
223             } else {
224 6     6 0 14 return $self->flatten_rendered_for_content_blocks(@_);
225 6         17 }
226             }
227              
228 6     6 0 29  
229             my ($self, $name, $value) = @_;
230             Module::Runtime::use_module($self->_content_exception_class)
231 42     42 0 55 ->throw(content_name=>$name, content_msg=>'Content block is already defined') if $self->_content_exists($name);
232 42         65 $self->ctx->stash->{view_blocks}{$name} = $self->render_content_value($value);
  42         707  
233             return;
234             }
235              
236 12     12 1 939 my ($self, $name, $value) = @_;
237 12 100       36 Module::Runtime::use_module($self->_content_exception_class)
238 12 100       31 ->throw(content_name=>$name, content_msg=>'Content block doesnt exist for appending') unless $self->_content_exists($name);
239             $self->ctx->stash->{view_blocks}{$name} .= $self->render_content_value($value);
240             return;
241 12 100       299 }
242              
243             my ($self, $name, $value) = @_;
244             Module::Runtime::use_module($self->_content_exception_class)
245             ->throw(content_name=>$name, content_msg=>'Content block doesnt exist for prepending') unless $self->_content_exists($name);
246 24     24 0 633 $self->ctx->stash->{view_blocks}{$name} = $self->render_content_value($value) . $self->ctx->stash->{view_blocks}{$name};
247 24 100 100     88 return;
248 6         17 }
249              
250 18         67  
251             my ($self, $name, $value) = @_;
252             Module::Runtime::use_module($self->_content_exception_class)
253             ->throw(content_name=>$name, content_msg=>'Content block doesnt exist for replacing') unless $self->_content_exists($name);
254 24     24 0 80 $self->ctx->stash->{view_blocks}{$name} = $self->render_content_value($value);
255             return;
256             }
257 6     6 1 40  
258 6 50       17 my ($self, $name, $value) = @_;
259             Module::Runtime::use_module($self->_content_exception_class)
260 6         344 ->throw(content_name=>$name, content_msg=>'Content block doesnt exist') unless $self->_content_exists($name);
261 6         365 $self->ctx->stash->{view_blocks}{$name} = $self->render_content_value($value, $self->ctx->stash->{view_blocks}{$name});
262             return;
263             }
264              
265 6     6 1 62  
266 6 50       23 my ($self, $name) = @_;
267             return exists $self->ctx->stash->{view_blocks}{$name} ? 1:0;
268 6         448 }
269 6         13  
270              
271             __PACKAGE__->meta->make_immutable;
272              
273 6     6 0 54 =head1 NAME
274 6 50       15
275             Catalyst::View::Template::BasePerRequest - Catalyst base view for per request, strongly typed templates
276 6         322  
277 6         332 =head1 SYNOPSIS
278              
279             package Example::View::Hello;
280              
281             use Moose;
282 0     0 1 0  
283 0 0       0 extends 'Catalyst::View::BasePerRequest';
284              
285 0         0 has name => (is=>'ro', required=>1);
286 0         0 has age => (is=>'ro', required=>1);
287              
288             sub render {
289             my ($self, $c) = @_;
290 6     6 1 56 return "<div>Hello @{[ $self->name] }",
291 6 50       11 "I see you are @{[ $self->age]} years old!</div>";
292             }
293 6         455  
294 6         306 __PACKAGE__->config(
295             content_type=>'text/html',
296             status_codes=>[200]
297 0     0   0 );
298              
299             __PACKAGE__->meta->make_immutable();
300 24     24   35  
301 24 100       590 One way to use it in a controller:
302              
303             package Example::Controller::Root;
304 0     0 1    
305             use Moose;
306             use MooseX::MethodAttributes;
307              
308             extends 'Catalyst::Controller';
309              
310             sub root :Chained(/) PathPart('') CaptureArgs(0) { }
311              
312             sub hello :Chained(root) Args(0) {
313             my ($self, $c) = @_;
314             return $c->view(Hello =>
315             name => 'John',
316             age => 53
317             )->http_ok;
318             }
319              
320             __PACKAGE__->config(namespace=>'');
321             __PACKAGE__->meta->make_immutable;
322              
323             =head1 DESCRIPTION
324              
325             B<NOTE>: This is early access code. Although it's based on several other internal projects
326             which I have iterated over this concept for a number of years I still reserve the right
327             to make breaking changes as needed.
328              
329             B<NOTE>: You probably won't actually use this directly, it's intended to be a base framework
330             for building / prototyping strongly typed / per request views in L<Catalyst>. This
331             documentation serves as an overview of the concept. In particular please note that
332             this code does not address any issues around HTML / Javascript injection attacks or
333             provides any auto escaping. You'll need to bake those features into whatever you
334             build on top of this. Because of this the following documentation is light and is mostly
335             intended to help anyone who is planning to build something on top of this framework
336             rather than use it directly.
337              
338             B<NOTE>: This distribution's C</example> directory gives you a toy prototype using L<HTML::Tags>
339             as the basis to a view as well as some raw examples using this code directly (again,
340             not recommended for anything other than learning).
341              
342             In a classic L<Catalyst> application with server side templates, the canonical approach
343             is to use a 'view' as a sort of handler for an underlying template system (such as
344             L<Template::Toolkit> or L<Xslate>) and to send data to this template by populating
345             the stash. These views are very lean, and in general don't provide much in the way
346             of view logic processing; they generally are just a thin proxy for the underlying
347             templating system.
348              
349             This approach has the upside of being very simple to understand and in general works
350             ok with a simple websites. There are however downsides as your site becomes more
351             complex. First of all the stash as a means to pass data from the Controller to the
352             template can be fragile. For example just making a simple typo in the stash key
353             can break your templates in ways that might not be easy to figure out. Also your
354             template can't enforce its requirements very easily (and it's not easy for someone
355             working in the controller to know exactly what things need to go into the stash in
356             order for the template to function as desired.) The view itself has no way of
357             providing view / display oriented logic; generally that logic ends up creeping back up
358             into the controller in ways that break the notion of MVC's separation of concerns.
359              
360             Lastly the controller doesn't have a defined API with the view. All it can ask the view
361             is 'go ahead and process yourself using the current context' and all it gets back from
362             the view is a string response. If the controller wishes to introspect this response
363             or modify it in some way prior to it being sent back to the client, you have few options
364             apart from using regular expression matching to try and extract the required information
365             or to modify the response string.
366              
367             Basically the classic approach works acceptable well for a simple website but starts to
368             break down as your site becomes more complicated.
369              
370             An alternative approach, which is explored in this distribution, is to have a defined view for
371             each desired response and for it to define an explicit API that the controller uses to provide the required
372             and optional data to the view. This defined view can further define its own methods
373             used to generate suitable information for display. Such an approach is more initial work
374             as well as learning for the website developers, but in the long term it can provide
375             an easier path to sustainable development and maintainence with hopefully fewer bugs
376             and overall site issues.
377              
378             =head1 EXAMPLE: Basic
379              
380             The most minimal thing your view must provide in a C<render> method. This method gets
381             the view object and the context (it can also receive additional arguments if this view is
382             being called from other views as a wrapper or parent view; more on that later).
383              
384             The C<render> method should return a string or array of strings suitable for the body of
385             the response> B<NOTE> if you return an array of strings we flatten the array into a single
386             string since the C<body> method of L<Catalyst::Response> can't take an array.
387              
388             Here's a minimal example:
389              
390             package Example::View::Hello;
391              
392             use Moose;
393              
394             extends 'Catalyst::View::BasePerRequest';
395              
396             sub render {
397             my ($self, $c) = @_;
398             return "<p>Hello</p>";
399             }
400              
401             __PACKAGE__->config(content_type=>'text/html');
402              
403             And here's an example view with attributes:
404              
405             package Example::View::HelloPerson;
406              
407             use Moose;
408              
409             extends 'Catalyst::View::BasePerRequest';
410              
411             has name => (is=>'ro', required=>1);
412              
413             sub render {
414             my ($self, $c) = @_;
415             return qq[
416             <div>
417             Hello @{[ $self->name ]}
418             </div>];
419             }
420              
421             __PACKAGE__->meta->make_immutable();
422              
423             One way to invoke this view from the controller using the traditional C<forward> method:
424              
425             package Example::Controller::Root;
426              
427             use Moose;
428             use MooseX::MethodAttributes;
429              
430             extends 'Catalyst::Controller';
431              
432             sub root :Chained(/) PathPart('') CaptureArgs(0) { }
433              
434             sub hello :Chained(root) Args(0) {
435             my ($self, $c) = @_;
436             my $view = $c->view(HelloPerson => (name => 'John'));
437             return $c->forward($view);
438             }
439              
440             __PACKAGE__->config(namespace=>'');
441             __PACKAGE__->meta->make_immutable;
442              
443             Alternatively using L</"RESPONSE HELPERS">:
444              
445             package Example::Controller::Root;
446              
447             use Moose;
448             use MooseX::MethodAttributes;
449              
450             extends 'Catalyst::Controller';
451              
452             sub root :Chained(/) PathPart('') CaptureArgs(0) { }
453              
454             sub hello :Chained(root) Args(0) {
455             my ($self, $c) = @_;
456             return $c->view(HelloPerson => (name => 'John'))->http_ok;
457             }
458              
459             __PACKAGE__->config(namespace=>'');
460             __PACKAGE__->meta->make_immutable;
461              
462             =head1 ATTRIBUTES
463              
464             The following Moose attributes are considered part of this classes public API
465              
466             =head2 app
467              
468             The string namespace of your L<Catalyst> application.
469              
470             =head2 ctx
471              
472             The current L<Catalyst> context
473              
474             =head2 root
475              
476             The root view object (that is the top view that was called first, usually from
477             the controller).
478              
479             =head2 parent
480              
481             =head2 has_parent
482              
483             If the view was called from another view, that first view is set as the parent.
484              
485             =head2 injected_views
486              
487             =head2 has_injected_views
488              
489             An arrayref of the method names associated with any injected views.
490              
491             =head1 METHODS
492              
493             The following methods are considered part of this classes public API
494              
495             =head2 process
496              
497             Renders a view and sets up the response object. Generally this is called from a
498             controller via the C<forward> method and not directly:
499              
500             $c->forward($view);
501              
502             =head2 respond
503              
504             Accepts an HTTP status code and an arrayref of key / values used to set HTTP headers for a
505             response. Example:
506              
507             $view->respond(201, [ location=>$url ]);
508              
509             Returns the view object to make it easier to do method chaining
510              
511             =head2 detach
512              
513             Just a shortcut to ->detach via the context
514              
515             =head1 CONTENT BLOCK HELPERS
516              
517             Content block helpers are an optional feature to make it easy to create and populate content
518             areas between different views. Although you can also do this with object attributes you may
519             wish to separate template / text from data. Example:
520              
521             package Example::View::Layout;
522              
523             use Moose;
524              
525             extends 'Catalyst::View::BasePerRequest';
526              
527             has title => (is=>'ro', required=>1, default=>'Missing Title');
528              
529             sub render {
530             my ($self, $c, $inner) = @_;
531             return "
532             <html>
533             <head>
534             <title>@{[ $self->title ]}</title>
535             @{[ $self->content('css') ]}
536             </head>
537             <body>$inner</body>
538             </html>";
539             }
540              
541             __PACKAGE__->config(content_type=>'text/html');
542             __PACKAGE__->meta->make_immutable();
543              
544             package Example::View::Hello;
545              
546             use Moose;
547              
548             extends 'Catalyst::View::BasePerRequest';
549              
550             has name => (is=>'ro', required=>1);
551              
552             sub render {
553             my ($self, $c) = @_;
554             return $c->view(Layout => title=>'Hello', sub {
555             my $layout = shift;
556             $self->content_for('css', "<style>...</style>");
557             return "<div>Hello @{[ $self->name ]}!</div>";
558             });
559             }
560              
561             __PACKAGE__->config(content_type=>'text/html', status_codes=>[200]);
562             __PACKAGE__->meta->make_immutable();
563              
564             =head2 content
565              
566             Examples:
567              
568             $self->content($name);
569             $self->content($name, +{ default=>'No main content' });
570              
571             Gets a content block string by '$name'. If the block has not been defined returns either
572             a zero length string or whatever you set the default key of the hashref options to.
573              
574             =head2 content_for
575              
576             Sets a named content block or throws an exception if the content block already exists.
577              
578             =head2 content_append
579              
580             Appends to a named content block or throws an exception if the content block doesn't exist.
581              
582             =head2 content_replace
583              
584             Replaces a named content block or throws an exception if the content block doesn't exist.
585              
586             =head2 content_around
587              
588             Wraps an existing content with new content. Throws an exception if the named content block doesn't exist.
589              
590             $self->content_around('footer', sub {
591             my $footer = shift;
592             return "wrapped $footer end wrap";
593             });
594              
595             =head1 VIEW INJECTION
596              
597             Usually when building a website of more than toy complexity you will find that you will
598             decompose your site into sub views and view wrappers. Although you can call the C<view>
599             method on the context, I think its more in the spirit of the idea of a strong or structured
600             view to have a view declare upfront what views its calling as sub views. That lets you
601             have more central control over view initalization and decouples how you are calling your
602             views from the actual underlying views. It can also tidy up some of the code and lastly
603             makes it easy to immediately know what views are needed for the current one. This can
604             help with later refactoring (I've worked on projects where sub views got detached from
605             actual use but nobody ever cleaned them up.)
606              
607             To inject a view into the current one, you need to declare it in configuration:
608              
609             __PACKAGE__->config(
610             content_type=>'text/html',
611             status_codes=>[200,404,400],
612             views=>+{
613             layout1 => [ Layout => sub { my ($self, $c) = @_; return title=>'Hey!' } ],
614             layout2 => [ Layout => (title=>'Yeah!') ],
615             layout3 => 'Layout',
616             },
617             );
618              
619             Basically this is a hashref under the C<views> key, where each key in the hashref is the name
620             of the method you are injecting into the current view which is responsible for creating the
621             sub view and the value is one of three options:
622              
623             =over
624              
625             =item A scalar value
626              
627             __PACKAGE__->config(
628             content_type=>'text/html',
629             status_codes=>[200,404,400],
630             views=>+{
631             layout => 'Layout',
632             },
633             );
634              
635             This is the simplest option, it just injects a method that will call the named view and pass
636             any arguments from the method but does not add any global arguments.
637              
638             =item An arrayref
639              
640             __PACKAGE__->config(
641             content_type=>'text/html',
642             status_codes=>[200,404,400],
643             views=>+{
644             layout => [ Layout => (title=>'Yeah!') ],
645             },
646             );
647              
648             This option allows you to set some argument defaults to the view called. The first item in the
649             arrayref must be the real name of the view, followed by arguments which are merged with any provided
650             to the method.
651              
652             =item A coderef
653              
654             __PACKAGE__->config(
655             content_type=>'text/html',
656             status_codes=>[200,404,400],
657             views=>+{
658             layout => [ Layout => sub { my ($self, $c) = @_; return title=>'Hey!' } ],
659             },
660             );
661              
662             The most complex option, you should probably reserve for very special needs. Basically this coderef
663             will be called with the current view instance and Catalyst context; it should return arguments which
664             with then be merged and treated as in the arrayref option.
665              
666             =back
667              
668             Now you can call for the sub view via a simple method call on the view, rather than via the context:
669              
670             package Example::View::Hello;
671              
672             use Moose;
673              
674             extends 'Catalyst::View::BasePerRequest';
675              
676             has name => (is=>'ro', required=>1);
677             has age => (is=>'ro', required=>1);
678              
679              
680             sub render {
681             my ($self, $c) = @_;
682              
683             return $self->layout(title=>'Hello!', sub {
684             my $layout = shift;
685             return "Hello @{[$self->name]}; you are @{[$self->age]} years old!";
686             });
687             }
688              
689              
690             __PACKAGE__->config(
691             content_type=>'text/html',
692             status_codes=>[200,404,400],
693             views=>+{
694             layout => 'Layout',
695             },
696             );
697              
698             __PACKAGE__->meta->make_immutable;
699              
700              
701             =head1 RESPONSE HELPERS
702              
703             When you create a view instance the actual response is not send to the client until
704             the L</respond> method is called (either directly, via L</process> or thru the generated
705             response helpers).
706              
707             Response helpers are just methods that call L</respond> with the correct status code
708             and using a more easy to remember name (and possibly a more self documenting one).
709              
710             For example:
711              
712             $c->view('Login')->http_ok;
713              
714             calls the L</respond> method with the expected http status code. You can also pass
715             arguments to the response helper which are send to L</respond> and used to add HTTP
716             headers to the response.
717              
718             $c->view("NewUser")
719             ->http_created(location=>$url);
720              
721             Please note that calling a response helper only sets up the response object, it doesn't
722             stop any future actions in you controller. If you really want to stop action processing
723             you'll need to call L</detach>:
724              
725             return $c->view("Error")
726             ->http_bad_request
727             ->detach;
728              
729             If you don't want to generate the response yet (perhaps you'll leave that to a global 'end'
730             action) you can use the 'set_http_$STATUS' helpers instead which wil just set the response
731             status.
732              
733             return $c->view("Error")
734             ->set_http_bad_request
735             ->detach;
736              
737             Response helpers are just lowercased names you'll find for the codes listed in L<HTTP::Status>.
738             Some of the most common ones I find in my code:
739              
740             http_ok
741             http_created
742             http_bad_request
743             http_unauthorized
744             http_not_found
745             http_internal_server_error
746              
747             By default we create response helpers for all the status codes in L<HTTP::Status>. However
748             if you set the C<status_codes> configuration key (see L</status_codes>) you can limit the
749             generated helpers to specific codes. This can be useful since most views are only meaningful
750             with a limited set of response codes.
751              
752             =head1 RUNTIME HOOKS
753            
754             This class defines the following method hooks you may optionally defined in your
755             view subclass in order to control or otherwise influence how the view works.
756            
757             =head2 $class->modify_init_args($app, $args)
758            
759             Runs when C<COMPONENT> is called during C<setup_components>. This gets a reference
760             to the merged arguments from all configuration. You should return this reference
761             after modification.
762              
763             This is for modifying or adding arguments that are application scoped rather than context
764             scoped.
765              
766             =head2 prepare_build_args
767              
768             This method will be called (if defined) by the factory class during build time. It can be used
769             to inject args and modify args. It gets the context and C<@args> as arguments and should return
770             all the arguments you want to pass to C<new>. Example:
771              
772             sub prepare_build_args {
773             my ($class, $c, @args) = @_;
774             # Mess with @args
775             return @args;
776             }
777              
778             =head2 build
779              
780             Receives the initialization hash and should return a new instance of the the view. By default this
781             just calls C<new> on the class with the hash of args but if you need to call some other method or
782             have some complex initialization work that can't be handled with L</prepare_build_args> you can
783             override.
784              
785             =head1 CONFIGURATION
786            
787             This Catalyst Component supports the following configuation.
788            
789             =head2 content_type
790              
791             The HTTP content type of the response. For example 'text/html'. Required.
792              
793             =head2 status_codes
794            
795             An ArrayRef of HTTP status codes used to provide response helpers. This is optional
796             but it allows you to specify the permitted HTTP response codes that a template can
797             generate. for example a NotFound view probably makes no sense to return anything
798             other than a 404 Not Found code.
799            
800             =head1 ALSO SEE
801            
802             L<Catalyst>
803              
804             =head1 AUTHORS & COPYRIGHT
805            
806             John Napiorkowski L<email:jjnapiork@cpan.org>
807            
808             =head1 LICENSE
809            
810             Copyright 2022, John Napiorkowski L<email:jjnapiork@cpan.org>
811            
812             This library is free software; you can redistribute it and/or modify
813             it under the same terms as Perl itself.
814            
815             =cut