File Coverage

blib/lib/Catalyst/View/BasePerRequest.pm
Criterion Covered Total %
statement 128 523 24.4
branch 39 206 18.9
condition 9 24 37.5
subroutine 33 186 17.7
pod 10 177 5.6
total 219 1116 19.6


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