File Coverage

blib/lib/Catalyst/View/Template/Pure.pm
Criterion Covered Total %
statement 150 181 82.8
branch 36 62 58.0
condition 3 6 50.0
subroutine 25 34 73.5
pod 7 21 33.3
total 221 304 72.7


line stmt bran cond sub pod time code
1 2     2   1209 use strict;
  2         2  
  2         49  
2 2     2   6 use warnings;
  2         2  
  2         68  
3              
4             package Catalyst::View::Template::Pure;
5              
6 2     2   6 use Scalar::Util qw/blessed refaddr weaken/;
  2         2  
  2         151  
7 2     2   7 use Catalyst::Utils;
  2         2  
  2         35  
8 2     2   6 use HTTP::Status ();
  2         0  
  2         20  
9 2     2   5 use File::Spec;
  2         2  
  2         30  
10 2     2   6 use Mojo::DOM58;
  2         1  
  2         31  
11 2     2   844 use Template::Pure::ParseUtils;
  2         2290  
  2         55  
12 2     2   717 use Template::Pure::DataContext;
  2         2734  
  2         44  
13              
14 2     2   8 use base 'Catalyst::View';
  2         2  
  2         758  
15              
16             our $VERSION = '0.015';
17              
18             sub COMPONENT {
19 4     4 1 450789 my ($class, $app, $args) = @_;
20 4         12 $args = $class->merge_config_hashes($class->config, $args);
21 4 50       285 $args = $class->modify_init_args($app, $args) if $class->can('modify_init_args');
22 4         21 $class->inject_http_status_helpers($args);
23 4         19 $class->load_auto_template($app, $args);
24 4         24 $class->find_fields;
25              
26 4         13 return bless $args, $class;
27             }
28              
29             my @fields;
30             sub find_fields {
31 4     4 0 5 my $class = shift;
32 4         18 for ($class->meta->get_all_attributes) {
33 14 50       392 next unless $_->has_init_arg;
34 14         61 push @fields, $_->init_arg;
35             }
36             }
37              
38             sub load_auto_template {
39 4     4 0 6 my ($class, $app, $args) = @_;
40 4         16 my @parts = split("::", $class);
41 4         7 my $filename = lc(pop @parts);
42            
43 4 100       10 if(delete $args->{auto_template_src}) {
44 2         11 my $file = $app->path_to('lib', @parts, $filename.'.html');
45 2         568 my $contents = $file->slurp;
46 2         374 my $dom = Mojo::DOM58->new($contents);
47 2 100       1044 if(my $node = $dom->at('pure-component')) {
48 1 50       114 if(my $script_node = $node->at('script')) {
49 1         126 $class->config(script => "$script_node");
50 1         113 $script_node->remove('script');
51             }
52 1 50       131 if(my $style_node = $node->at('style')) {
53 1         112 $class->config(style => "$style_node");
54 1         88 $style_node->remove('style');
55             }
56 1         100 $contents = $node->content;
57             }
58 2         462 $class->config(template => $contents);
59             }
60 4 50       153 if(delete $args->{auto_script_src}) {
61 0         0 my $file = $app->path_to('lib', @parts, $filename.'.js');
62 0         0 $class->config(script => $file->slurp);
63             }
64 4 50       14 if(delete $args->{auto_style_src}) {
65 0         0 my $file = $app->path_to('lib', @parts, $filename.'.css');
66 0         0 $class->config(style => $file->slurp);
67             }
68             }
69              
70             sub inject_http_status_helpers {
71 4     4 0 6 my ($class, $args) = @_;
72 4 100       13 return unless $args->{returns_status};
73 2         6 foreach my $helper( grep { $_=~/^http/i} @HTTP::Status::EXPORT_OK) {
  122         121  
74 118         102 my $subname = lc $helper;
75 118         170 my $code = HTTP::Status->$helper;
76 118         101 my $codename = "http_".$code;
77 118 50       68 if(grep { $code == $_ } @{ $args->{returns_status}||[]}) {
  118 100       167  
  118         178  
78 2     2 0 3 eval "sub ${\$class}::${\$subname} { return shift->response(HTTP::Status::$helper,\@_) }";
  2         6  
  2         144  
  2         178  
79 2     0 0 5 eval "sub ${\$class}::${\$codename} { return shift->response(HTTP::Status::$helper,\@_) }";
  2         7  
  2         75  
  0         0  
80             }
81             }
82             }
83              
84             sub ACCEPT_CONTEXT {
85 7     7 1 184360 my ($self, $c, @args) = @_;
86              
87 7         13 my %args = ();
88 7 50       26 if(scalar(@args) % 2) {
89 0         0 my $proto = shift @args;
90             # TODO This needs to enforce the duck type
91 0         0 foreach my $field (@fields) {
92 0 0       0 if(ref $proto eq 'HASH') {
93 0 0       0 $args{$field} = $proto->{$field} if exists $proto->{$field};
94             } else {
95 0 0       0 if(my $cb = $proto->can($field)) {
96 0         0 $args{$field} = $proto->$field;
97             }
98             }
99             }
100             }
101              
102 7         18 %args = (%args, @args);
103 7         21 my $args = $self->merge_config_hashes($self->config, \%args);
104 7 50       514 $args = $self->modify_context_args($c, $args) if $self->can('modify_context_args');
105 7 50       36 $self->handle_request($c, %$args) if $self->can('handle_request');
106              
107 7         8 my $template;
108 7 50       14 if(exists($args->{template})) {
    0          
109 7         14 $template = delete ($args->{template});
110             } elsif(exists($args->{template_src})) {
111 0         0 $template = (delete $args->{template_src})->slurp;
112             } else {
113 0         0 die "Can't find a template for your View";
114             }
115              
116 7         7 my $directives = delete $args->{directives};
117 7         10 my $filters = delete $args->{filters};
118             my $pure_class = exists($args->{pure_class}) ?
119 7 100       13 delete($args->{pure_class}) :
120             'Template::Pure';
121              
122 7         15 Catalyst::Utils::ensure_class_loaded($pure_class);
123              
124 7 50       18821 my $key = blessed($self) ? refaddr($self) : $self;
125              
126 7 50       22 if(blessed $c) {
127              
128 7         14 my $stash_key = "__Pure_${key}";
129              
130 7 100       19 if(my $clear = delete($args{clear_stash})) {
131 2         6 delete $c->stash->{$stash_key};
132             }
133              
134 7         123 weaken $c;
135 7   66     20 $c->stash->{$stash_key} ||= do {
136              
137             ## TODO Could we not optimize by building this just once per application
138             ## scope?
139              
140             my $view = ref($self)->new(
141 5         16 %{$args},
142 5         301 %{$c->stash},
  5         12  
143             ctx => $c,
144             );
145              
146 5         1237 weaken(my $weak_view = $view);
147 5         27 my $pure = $pure_class->new(
148             template => $template,
149             directives => $directives,
150             filters => $filters,
151             components => $self->build_comp_hash($c, $view),
152             view => $weak_view,
153             %$args,
154             );
155              
156 5         4825 $view->{pure} = $pure;
157 5         17 $view;
158             };
159 7         112 return $c->stash->{$stash_key};
160             } else {
161 0         0 die "Can't make this class without a context";
162             }
163             }
164              
165             sub build_comp_hash {
166 5     5 0 6 my ($self, $c, $view) = @_;
167 5 100       18 return $self->{__components} if $self->{__components};
168             my %components = (
169             map {
170 4         22 my $v = $_;
  8         365  
171 8         11 my $key = lc($v);
172 8         10 $key =~s/::/-/g;
173             $key => sub {
174 2     2   1862 my ($pure, %params) = @_;
175 2         12 my $data = Template::Pure::DataContext->new($view);
176 2 50       12 foreach $key (%{$params{node}->attr ||+{}}) {
  2         8  
177 4 100 33     87 next unless $key && $params{$key};
178 2 100       8 next unless my $proto = ($params{$key} =~m/^\$(.+)$/)[0];
179 1         5 my %spec = Template::Pure::ParseUtils::parse_data_spec($proto);
180 1         61 $params{$key} = $data->at(%spec)->value;
181             }
182              
183 2         25 return $c->view($v, %params, clear_stash=>1);
184             }
185 8         36 } ($c->views),
186             );
187 4         9 $self->{__components} = \%components;
188 4         24 return \%components;
189             }
190              
191             sub apply {
192 0     0 1 0 my $self = shift;
193             my @args = (@_,
194             template => $self->render,
195 0         0 %{$self->{ctx}->stash});
  0         0  
196 0         0 return $self->{ctx}->view(@args);
197             }
198              
199             sub wrap {
200 0     0 1 0 my $self = shift;
201             my @args = (@_,
202             content => $self->render,
203 0         0 %{$self->{ctx}->stash});
  0         0  
204 0         0 return $self->{ctx}->view(@args);
205             }
206              
207             sub response {
208 2     2 1 5 my ($self, $status, @proto) = @_;
209 2 50       10 die "You need a context to build a response" unless $self->{ctx};
210              
211 2         18 my $res = $self->{ctx}->res;
212 2 50       84 $status = $res->status if $res->status != 200;
213              
214 2 50       218 if(ref($proto[0]) eq 'ARRAY') {
215 0         0 my @headers = @{shift @proto};
  0         0  
216 0         0 $res->headers->push_header(@headers);
217             }
218              
219 2 50       9 $res->content_type('text/html') unless $res->content_type;
220 2         654 my $body = $res->body($self->render);
221              
222 2         20 return $self;
223             }
224              
225 0     0 0 0 sub detach { shift->{ctx}->detach }
226              
227             sub render {
228 5     5 0 7 my ($self, $data) = @_;
229 5         125 $self->{ctx}->stats->profile(begin => "=> ".Catalyst::Utils::class2classsuffix($self->catalyst_component_name)."->Render");
230              
231             # quite possible I should do something with $data...
232 5         454 my $string = $self->{pure}->render($self);
233 5         14775 $self->{ctx}->stats->profile(end => "=> ".Catalyst::Utils::class2classsuffix($self->catalyst_component_name)."->Render");
234 5         442 return $string;
235             }
236              
237             sub TO_HTML {
238 1     1 0 88 my ($self, $pure, $dom, $data) = @_;
239             return $self->{pure}->encoded_string(
240 1         6 $self->render($self));
241             }
242              
243             sub Views {
244 1     1 0 177 my $self = shift;
245             my %views = (
246             map {
247 2         63 my $v = $_;
248             $v => sub {
249 1     1   351 my ($pure, $dom, $data) = @_;
250             # TODO $data can be an object....
251 1         5 $self->{ctx}->view($v, %$data);
252             }
253 2         8 } ($self->{ctx}->views)
254 1         4 );
255 1         4 return \%views;
256             }
257              
258             # Proxy these here for now. I assume eventually will nee
259             # a subclass just for components
260             #sub prepare_render_callback { shift->{pure}->prepare_render_callback }
261              
262             sub prepare_render_callback {
263 2     2 0 179 my $self = shift;
264             return sub {
265 2     2   2251 my ($t, $dom, $data) = @_;
266 2         6 $self->{pure}->process_root($dom->root, $data);
267 2         1449 $t->encoded_string($self->render($data));
268 2         9 };
269             }
270              
271 0     0 0   sub style_fragment { shift->{pure}->style_fragment }
272 0     0 0   sub script_fragment { shift->{pure}->script_fragment }
273 0     0 1   sub ctx { return shift->{ctx} }
274              
275             sub process {
276 0     0 1   my ($self, $c, @args) = @_;
277 0           $self->response(200, @args);
278             }
279              
280       0 0   sub headers {
281             # TODO let you add headders
282             }
283             1;
284              
285             =head1 NAME
286              
287             Catalyst::View::Template::Pure - Catalyst view adaptor for Template::Pure
288              
289             =head1 SYNOPSIS
290              
291             package MyApp::View::Story;
292              
293             use Moose;
294             use HTTP::Status qw(:constants);
295              
296             extends 'Catalyst::View::Template::Pure';
297              
298             has [qw/title body timestamp/] => (is=>'ro', required=>1);
299              
300             sub current { scalar localtime }
301              
302             __PACKAGE__->config(
303             timestamp => scalar(localtime),
304             returns_status => [HTTP_OK],
305             template => q[
306             <!doctype html>
307             <html lang="en">
308             <head>
309             <title>Title Goes Here</title>
310             </head>
311             <body>
312             <div id="main">Content goes here!</div>
313             <div id="current">Current Localtime: </div>
314             <div id="timestamp">Server Started on: </div>
315             </body>
316             </html>
317             ],
318             directives => [
319             'title' => 'title',
320             '#main' => 'body',
321             '#current+' => 'current',
322             '#timestamp+' => 'timestamp',
323             ],
324             );
325              
326             __PACKAGE__->meta->make_immutable
327              
328             Create a controller that uses this view:
329              
330             package MyApp::Controller::Story;
331              
332             use Moose;
333             use MooseX::MethodAttributes;
334              
335             extends 'Catalyst::Controller';
336              
337             sub display_story :Local Args(0) {
338             my ($self, $c) = @_;
339             $c->view('Story',
340             title => 'A Dark and Stormy Night...',
341             body => 'It was a dark and stormy night. Suddenly...',
342             )->http_ok;
343             }
344              
345             __PACKAGE__->meta->make_immutable
346              
347             When hitting a page that activates the 'display_story' action, returns:
348              
349             <!doctype html>
350             <html lang="en">
351             <head>
352             <title>A Dark and Stormy Night...</title>
353             </head>
354             <body>
355             <div id="main">It was a dark and stormy night. Suddenly...</div>
356             <div id="current">Current Localtime: July 29, 2016 11:30:34</div>
357             <div id="timestamp">Server Started on: July 29, 2016 11:30:00</div>
358             </body>
359             </html>
360              
361             (Obviously the 'localtime' information will vary ;)
362              
363             =head1 DESCRIPTION
364              
365             L<Catalyst::View::Template::Pure> is an adaptor for L<Template::Pure> for the L<Catalyst>
366             web development framework. L<Template::Pure> is an HTML templating system that fully
367             separates concerns between markup (the HTML), transformations on that markup (called
368             'directives') and data that the directives use on the template to return a document.
369              
370             I highly recommend you review the documentation for L<Template::Pure> if you wish to gain a
371             deeper understanding of how this all works. The following information is specific to how
372             we adapt L<Template::Pure> to run under L<Catalyst>; as a result it will assume you already
373             know the basics of creating templates and directives using L<Template::Pure>
374              
375             B<NOTE>: Like L<Template::Pure> I consider this work to be early access and reserve the
376             right to make changes needed to achieve stability and production quality. In general I feel
377             pretty good about the interface but there's likely to be changes around speed optimization,
378             error reporting and in particular web components are not fully baked. I recommend if you
379             are using this to avoid deeply hooking into internals since that stuff is most likely to
380             change. If you are using this for your work please let me know how its going. Don't find bugs
381             surprising, but please report them!
382              
383             =head1 CREATING AND USING VIEWS
384              
385             In many template adaptors for L<Catalyst> you create a single 'View' which is a sort of
386             factory that processes a whole bunch of templates (typically files in a directory under
387             $APPHOME/root). Variables are passed to the view view the Catalyst stash. Choosing the
388             template to process is typically via some convention based on the action path and/or via
389             a special stash key.
390              
391             This system works fine to a point, but I've often found when a system gets complex (think
392             dozens of controllers and possible hundreds of templates) it gets messy. Because the
393             stash is not strongly typed you have no declared interface between the view and your
394             controller. This can be great for rapid development but a long term maintainance nightmare.
395             People often lose track of what is and isnt' in the stash for a given template (not to
396             mention the fact that a small typo will 'break' the interface between the stash and the
397             view template.
398              
399             L<Catalyst::View::Template::Pure> is a bit different. Instead of a single template
400             factory view, you need to make a view subclass per resource (that is, for each HTML
401             webpage you want to display). Additionally you will make a view for any of the
402             reusable bits that often make up a complex website, such as includes and master page
403             layouts. That sounds like a lot of views, and will seem wierd to you at first if you
404             are used to the old style 'one view class to rule the all'. The requirement to make a
405             new View subclass for each page or part of a page does add a bit of overhead to the
406             development process. The upside is that you are creating strongly types views that
407             can contain their own logic, defaults and anything else that can go into a Perl class.
408             This way you can enforce an interface between your views and the controllers that use
409             them. Over time the extra, original overhead should pay you back in less maintainance
410             issues and in greater code clarity.
411              
412             So here's the example! Lets create a simple view:
413              
414             package MyApp::View::Hello;
415              
416             use Moose;
417             use HTTP::Status qw(:constants);
418              
419             extends 'Catalyst::View::Template::Pure';
420              
421             has [qw/title name/] => (is=>'ro', required=>1);
422              
423             sub timestamp { scalar localtime }
424              
425             __PACKAGE__->config(
426             template => q[
427             <html>
428             <head>
429             <title>Title Goes Here</title>
430             </head>
431             <body>
432             <p>Hello <span id='name'>NAME</span>!<p>
433             <p>This page was generated on: </p>
434             </body>
435             </html>
436             ],
437             directives => [
438             'title' => 'title',
439             '#name' => 'name',
440             '#timestamp+' => 'timestamp',
441             ],
442             returns_status => [HTTP_OK],
443             );
444              
445             __PACKAGE__->meta->make_immutable;
446              
447             So this is a small view with just three bits of data that is used to create
448             an end result webpage. Two fields need to be passed to the view (title and name)
449             while the third one (timestamp) is generated locally by the view itself. The three
450             entries under the 'directives' key are instructions to L<Template::Pure> to run
451             an action at a particular CSS match in the templates HTML DOM (see documentation
452             for L<Template::Pure> for more details).
453              
454             B<NOTE> In this and most following examples the template is a literal string inside
455             the view under the C<template> configuration key. This is handy for demo and for
456             small views (such as includes) but your template authors may prefer to use a more standard
457             text file, in which case you can specify a path to the template via configuration options
458             C<template_src> or C<auto_template_src>; see L</CONFIGURATION>
459            
460             Lets use this in a controller:
461              
462             package MyApp::Controller::Hello;
463              
464             use Moose;
465             use MooseX::MethodAttributes;
466              
467             extends 'Catalyst::Controller';
468              
469             sub say_hello :Path('') Args(0) {
470             my ($self, $c) = @_;
471             $c->view('Hello',
472             title => 'Hello to You!',
473             name => 'John Napiorkowski',
474             )->http_ok;
475             }
476              
477             __PACKAGE__->meta->make_immutable;
478              
479             Again, if you are following a classic pattern in L<Catalyst> you might be using the
480             L<Catalyst::Action::RenderView> on a global 'end' action (typically in your
481             Root controller) to do the job of forwarding the request to a view. Then, the view
482             would decide on a template based on a few factors, such as the calling action's
483             private name. With L<Catalyst::View::Template::Pure> instead we are calling the view directly,
484             as well as directly sending the view's arguments call to the view, instead of via the
485             stash (although as we will see later, you can still use the stash and even the
486             L<Catalyst::Action::RenderView> approach if that is really the best setup for
487             your application).
488              
489             B<NOTE> An important distinction here to remember is that when you pass arguments to
490             the view, those arguments are not passed directly as data to the underlying
491             L<Template::Pure> object. Rather these arguments are combined with any local or global
492             configuration and used as arguments when calling ->new on the actual view component.
493             So arguments passed, even via the stash, as not directly exposed to the template, but
494             rather mediated via the actual view object. Only attributes and methods on the view
495             object are exposed to the template.
496              
497             In calling the view this way you setup a stronger association between your controller
498             and the view. This can add a lot of clarity to your code when you have very large
499             and complex websites. In addition the view returned is scoped 'Per Request', instead
500             of 'Per Application' like most common Catalyst views in use. 'Per Request' in this
501             case means that the first time you call for the view in a given request, we create
502             a new instance of that view from the arguments passed. Subsequent calls to the same
503             view will return the same instance created earlier. This can be very useful if you
504             have complex chained actions and wish to add information to a view over the course
505             of a number of actions in the chain. However when the response is finalized and
506             returned to the client, the current request goes out of scope which triggers DESTROY
507             on the view.
508              
509             Another useful thing about the fact that the view is scoped 'Per Request' is that
510             it contains a reference to the context. So in your custom view methods you can call
511             $self->ctx and get the context to do stuff like build links or even access models.
512             Just keep in mind you need to think carefully about what logic is proper to the
513             view and which is proper to the controller. In general if there is logic that
514             would be the same if the resource generated by the view was a different type (say
515             JSON or XML) then its likely that logic belongs in the controller. However I
516             encourage you to choose the approach that leads to clean and reusable code.
517              
518             Lastly, L<Catalyst::View::Template::Pure> allows you to specify the type of response
519             status code can be associated with this view. This can be useful when you want
520             to make it clear that a given view is an error response or for created resources.
521             To enable this feature you simple set the 'returns_status' configuration key to
522             an arrayref of the HTTP status codes allowed. This is simple a number (201 for
523             created, for example) but for clarity in the given example I've used L<HTTP::Status>
524             to give the allowed codes a friendly name. You can choose to follow this example
525             or not! As a futher timesaver, when you set allowed statuses, we will inject into
526             your view some helper methods to set the desired status. As in the given example:
527              
528             $c->view('Hello',
529             title => 'Hello to You!',
530             name => 'John Napiorkowski',
531             )->http_ok;
532              
533             We are setting $c->res->status(200). For people that prefer the actual code numbers
534             there is also ->http_200 injected if you are better with the number codes instead of
535             the friendly names but I recommend you choose one or the other approach for your project!
536              
537             Please keep in mind that calling ->http_ok (or any of the helper methods) does not
538             immediately finalize your response. If you want to immediately finalize the
539             response (say for example you are returning an error and want to stop processing the
540             remaining actions) you will need to $c->detach like normal. To make this a little
541             easier you can chain off the response helper like so:
542              
543             $c->view('NotFound')
544             ->http_404
545             ->detach;
546              
547             Sending a request that hits the 'say_hello' action would result in:
548              
549             <html>
550             <head>
551             <title>Hello to You!</title>
552             </head>
553             <body>
554             <p>Hello <span id='name'>John Napiorkowski</span>!<p>
555             <p>This page was generated on: Tue Aug 2 09:17:48 2016</p>
556             </body>
557             </html>
558              
559             (Of course the timestamp will vary based on when you run the code, this
560             was the result I got only at the time of writing this document).
561              
562             =head1 USING THE STASH
563              
564             If you are used to using the L<Catalyst> stash to pass information to your view
565             or you have complex chaining and like to build up data over many actions into the
566             stash, you may continue to do that. For example:
567              
568             sub say_hello :Path('') Args(0) {
569             my ($self, $c) = @_;
570             $c->stash(
571             title => 'Hello to You!',
572             name => 'John Napiorkowski',
573             );
574             $c->view('Hello')->http_ok;
575             }
576              
577             Would be the functional equal to the earlier example. However as noted those
578             arguments are not passed directly to the template as data, but rather passed as
579             initialization arguments to the ->new method when calling the view the first time
580             in a request. So you may still use the stash, but because the view is mediating
581             the stash data I believe we mitigate some of the stash's downsides (such as a lack
582             of strong typing, missing defined interface and issues with typos, for example).
583              
584             =head1 CHAINING TEMPLATE TRANFORMATIONS
585              
586             There are several ways to decompose your repeated or options template transforms
587             into reusable chunks, at the View level. Please see L<Template::Pure> for more
588             abour includes, wrappers and overlays. However there are often cases when the
589             decision to use or apply changes to your template best occur at the controller
590             level. For example you may wish to add some messaging to your template if a form
591             has incorrect data. In those cases you may apply additional Views. Applied views
592             will use as its starting template the results of the previous view. For example:
593              
594             sub process_form :POST Path('') Args(0) {
595             my ($self, $c) = @_;
596             my $v = $c->view('Login');
597              
598             if($c->model('Form')->is_valid) {
599             $v->http_ok;
600             } else {
601             $v->apply('IncorrectLogin')
602             ->http_bad_request
603             ->detach;
604             }
605             }
606              
607             You may chain as many applied views as you like, even using this technique to build up
608             an entire page of results. Chaining transformations this way can help you to avoid some
609             of the messy, complex logic that often creeps into our templates.
610              
611             =head1 MAPPING TEMPLATE ARGS FROM AN OBJECT
612              
613             Generally you send arguments to the View via the stash or via arguments on the view
614             call itself. This might sometimes lead to highly verbose calls:
615              
616             sub user :Path Args(1) {
617             my ($self, $c, $id) = @_:
618             my $user = $c->model('Schema::User')->find($id) ||
619             $c->view('NoUser')->http_bad_request->detach;
620              
621             $c->view('UserProfile',
622             name => $user->name,
623             age => $user->age,
624             location => $user->location,
625             ...,
626             );
627             }
628              
629             Listing each argument has the advantage of clarity but the verbosity can be distracting
630             and waste programmer time. So, in the case where a source object provides an interface
631             which is identical to the interface required by the view, you may just pass the object
632             and we will map required attributes for the view from method named on the object. For
633             example:
634              
635             sub user :Path Args(1) {
636             my ($self, $c, $id) = @_:
637             my $user = $c->model('Schema::User')->find($id) ||
638             $c->view('NoUser')->http_bad_request
639             ->detach;
640              
641             $c->view(UserProfile => $user)
642             ->http_ok;
643             }
644              
645             It is up to you to decide if this is creating too much structual binding between your
646             view and its model. You may or may not find it a useful convention.
647              
648             =head1 COMMON VIEW TASKS
649              
650             The following are suggestions regarding some of the more common tasks we need to
651             use a view for. Most of this is covered in L<Template::Pure> in greater detail,
652             but I wanted to show the minor 'twists' the Catalyst adaptor presents. Please
653             keep in mind the following are not the only ways to solve this problems, but just
654             what I think of as very straightfoward ways that are a good starting point for you
655             as you climb the learning curve with L<Template::Pure>
656              
657             =head2 Includes, Wrappers and Master Pages
658              
659             Generally when building a website you will break up common elements of the user
660             interface into re-usable chunks. For example its common to have some standard
661             elements for headers and footers, or to have a master page template that provides
662             a common page structure. L<Template::Pure> supports these via processing
663             instructions which appear inside the actual template or via the including of
664             actual template objects as values for you directive actions on in your data.
665              
666             The documentation for L<Template::Pure> covers these concepts and approaches in
667             general. However L<Catalyst::View::Template::Pure> provides a bit of assistance
668             with helper methods that are unique to this module and require explanation. Here's
669             an example of an include which creates a time stamp element in your page:
670              
671             package MyApp::View::Include;
672              
673             use Moose;
674              
675             extends 'Catalyst::View::Template::Pure';
676              
677             sub now { scalar localtime }
678              
679             __PACKAGE__->config(
680             template => q{
681             <div class="timestamp">The Time is now: </div>
682             },
683             directives => [
684             '.timestamp' => 'now'
685             ],
686             );
687              
688             __PACKAGE__->meta->make_immutable;
689              
690             Since this include is not intended to be used 'stand alone' we didn't bother to
691             set a 'returns_status' configuration.
692              
693             So there's a few ways to use this in a template.
694              
695             package MyApp::View::Hello;
696              
697             use Moose;
698             use HTTP::Status qw(:constants);
699              
700             extends 'Catalyst::View::Template::Pure';
701              
702             has 'name' => (is=>'ro', required=>1);
703              
704             __PACKAGE__->config(
705             returns_status => [HTTP_OK],
706             template => q{
707             <html>
708             <head>
709             <title>Hello</title>
710             </head>
711             <body>
712             <p id='hello'>Hello There </p>
713             <?pure-include src='Views.Include'?>
714             </body>
715             </html>
716             },
717             directives => [
718             '#hello' => 'name',
719             ],
720             );
721              
722             __PACKAGE__->meta->make_immutable;
723              
724             In this example we set the C<src> attribute for the include processing
725             instruction to a path off 'Views' which is a special method on the view that
726             returns access to all the other views that are loaded. So essentially any
727             view could serve as a source.
728              
729             The same approach would be used to set overlays and wrappers via processing
730             instructions.
731              
732             If using the C<Views> helper seems too flimsy an interface, you may instead
733             specify a view via an accessor, just like any other data.
734              
735             package MyApp::View::Hello;
736              
737             use Moose;
738             use HTTP::Status qw(:constants);
739              
740             extends 'Catalyst::View::Template::Pure';
741              
742             has 'name' => (is=>'ro', required=>1);
743              
744             sub include {
745             my $self = shift;
746             $self->ctx->view('Include');
747             }
748              
749             __PACKAGE__->config(
750             returns_status => [HTTP_OK],
751             template => q{
752             <html>
753             <head>
754             <title>Hello</title>
755             </head>
756             <body>
757             <p id='hello'>Hello There </p>
758             <?pure-include src='include' ?>
759             </body>
760             </html>
761             },
762             directives => [
763             '#hello' => 'name',
764             ],
765             );
766              
767             __PACKAGE__->meta->make_immutable;
768              
769             Just remember if your include expects arguments (and most will) you should pass
770             them in the view call.
771              
772             In fact you could allow one to pass the view C<src> include (or wrapper, or overlay)
773             from the controller, if you need more dynamic control:
774              
775             package MyApp::View::Hello;
776              
777             use Moose;
778             use HTTP::Status qw(:constants);
779              
780             extends 'Catalyst::View::Template::Pure';
781              
782             has 'name' => (is=>'ro', required=>1);
783             has 'include' => (is=>'ro', required=>1);
784              
785             __PACKAGE__->config(
786             returns_status => [HTTP_OK],
787             template => q{
788             <html>
789             <head>
790             <title>Hello</title>
791             </head>
792             <body>
793             <p id='hello'>Hello There </p>
794             <?pure-include src='include' ?>
795             </body>
796             </html>
797             },
798             directives => [
799             '#hello' => 'name',
800             ],
801             );
802              
803             __PACKAGE__->meta->make_immutable;
804              
805             package MyApp::Controller::Hello;
806              
807             use Moose;
808             use MooseX::Attributes;
809              
810             extends 'Catalyst::Controller';
811              
812             sub hello :Path('') {
813             my ($self, $ctx) = @_;
814             $ctx->view('Hello',
815             name => 'John',
816             include => $ctx->view('Include'));
817             }
818              
819             __PACKAGE__->meta->make_immutable;
820              
821             Even more fancy approaches could include setting up the required bits via
822             dependency injection (approaches for this in Catalyst are still somewhat
823             experimental, see L<Catalyst::Plugin::MapComponentDependencies>
824              
825             =head1 METHODS
826              
827             This class defines the following methods. Please note that response helpers
828             will be generated as well (http_ok, http_200, etc.) based on the contents of
829             your L<\returns_status> configuration settings.
830              
831             =head2 apply
832              
833             Takes a view name and optionally arguments that are passed to ->new. Used to
834             apply a view over the results of a previous one, allowing for chained views.
835             For example:
836              
837             $c->view('Base', %args)
838             ->apply('Sidebar', items => \@menu_items)
839             ->apply('Footer', copyright => 2016)
840             ->http_ok;
841              
842             When a view is used via 'apply', the result of the previous template becomes
843             the 'template' argument, even if that view defined its own template via
844             configuration. This is so that you can use the same view as standalone or as
845             part of a chain of transformations.
846              
847             Useful when you are building up a view over a number of actions in a chain or
848             when you need to programmatically control how a view is created from the
849             controller. You may also consider the use of includes and overlays inside your
850             view, or custom directive actions for more complex view building.
851              
852             =head2 wrap
853              
854             Used to pass the response on a template to another template, via a 'content'
855             argument. Similar to the 'wrapper' processing instruction. Example:
856              
857             package MyApp::View::Users;
858              
859             use Moose;
860              
861             extends 'Catalyst::View::Template::Pure';
862              
863             has [qw/name age location/] => (is=>'ro', required=>1);
864              
865             __PACKAGE__->config(
866             returns_status => [200],
867             template => q[
868             <dl>
869             <dt>Name</dt>
870             <dd id='name'></dd>
871             <dt>Age</dt>
872             <dd id='age'></dd>
873             <dt>Location</dt>
874             <dd id='location'></dd>
875             </dl>
876             ],
877             directives => [
878             '#name' => 'name',
879             '#age' => 'age',
880             '#location' => 'location',
881             ]
882             );
883              
884             package MyApp::View::HeaderFooter;
885              
886             use Moose;
887              
888             extends 'Catalyst::View::Template::Pure';
889              
890             has 'title' => (is=>'ro', isa=>'String');
891             has 'content' => (is=>'ro');
892              
893             __PACKAGE__->config(
894             returns_status => [200],
895             template => q[
896             <html>
897             <head>
898             <title>TITLE GOES HERE</title>
899             </head>
900             <body>
901             CONTENT GOES HERE
902             </body>
903             </html>
904             ],
905             directives => [
906             title => 'title',
907             body => 'content',
908             ]
909             );
910              
911             package MyApp::Controller::UserProfile;
912              
913             use Moose;
914             use MooseX::MethodAttributes;
915              
916             extends 'Catalyst::Controller';
917              
918             sub show_profile :Path('profile') Args(0) {
919             my ($self, $c) = @_;
920             $c->view('UserProfile', $user)
921             ->wrap('HeaderFooter', title=>'User Profile')
922             ->http_ok;
923             }
924              
925             Generates a response like (assuming C<$user> is an object that provides
926             C<name>, C<age> and C<location> with the sample values):
927              
928             <html>
929             <head>
930             <title>User Profile</title>
931             </head>
932             <body>
933             <dl>
934             <dt>Name</dt>
935             <dd id='name'>Mike Smith</dd>
936             <dt>Age</dt>
937             <dd id='age'>42</dd>
938             <dt>Location</dt>
939             <dd id='location'>UK</dd>
940             </dl>
941             </body>
942             </html>
943              
944             =head2 response
945              
946             Used to run the directives and actions on the template, setting information
947             into the L<Catalyst::Response> object such as body, status, headers, etc.
948             Example
949              
950             $c->view('Hello',
951             title => 'Hello There',
952             list => \@users )
953             ->response(200, %headers);
954              
955             This will populate the L<Catalyst::Response> status and headers, and render the
956             template into body. It will not finalized and send the response to the client.
957             If you need to stop processing immediately (for example you are creating some
958             sort of error response in a middle action in a chain) you need to $c->detach
959             or use the detach convenience method:
960              
961             $c->view('BadRequest',
962             title => 'Hello There',
963             list => \@users )
964             ->response(400, %headers)
965             ->detach;
966              
967             Often you will instead set the L</returns_status> configuration setting and
968             use a response helper instead of using it directly.
969              
970             $c->view('BadRequest',
971             title => 'Hello There',
972             list => \@users )
973             ->http_bad_request
974             ->detach;
975              
976             =head2 $response helpers
977              
978             In order to better purpose your views and to add some ease of use for your
979             programmers, you may specify what HTTP status codes a view is allowed to
980             return via the L</returns_status> configuration option. When you do this
981             we automatically generate response helper methods. For example if you set
982             C<returns_status> to [200,400] we will create methods C<http_ok>, C<http_200>,
983             C<http_bad_request> and C<http_400> into your view. This method will finalize
984             your response as well as return an object that you can call C<detach> upon
985             should you wish to short circuit any remaining actions.
986              
987             Lastly you may pass as arguments an array of HTTP headers:
988              
989             $c->view("NewUser")
990             ->http_created(location=>$url)
991             ->detach;
992              
993             =head2 ctx
994              
995             Lets your view access the current context object. Useful in a custom view method
996             when you need to access other models or context information. You should however
997             take care to consider if you might not be better off accessing this via the controller
998             and passing the information into the view.
999              
1000             sub include {
1001             my $self = shift;
1002             $self->ctx->view('Include');
1003             }
1004              
1005             =head1 COMPONENTS
1006              
1007             B<WARNING> Components are the most experimental aspect of L<Template::Pure>!
1008              
1009             Example Component View Class:
1010              
1011             package MyApp::View::Timestamp;
1012              
1013             use Moose;
1014             use DateTime;
1015              
1016             extends 'Catalyst::View::Template::Pure';
1017              
1018             has 'tz' => (is=>'ro', predicate=>'has_tz');
1019              
1020             sub time {
1021             my ($self) = @_;
1022             my $now = DateTime->now();
1023             $now->set_time_zone($self->tz)
1024             if $self->has_tz;
1025             return $now;
1026             }
1027              
1028             __PACKAGE__->config(
1029             pure_class => 'Template::Pure::Component',
1030             auto_template_src => 1,
1031             directives => [
1032             '.timestamp' => 'time',
1033             ],
1034             );
1035             __PACKAGE__->meta->make_immutable;
1036              
1037             And the associated template:
1038              
1039             <pure-component>
1040             <style>
1041             .timestamp {
1042             background:blue;
1043             }
1044             </style>
1045             <script>
1046             function alertit() {
1047             alert(1);
1048             }
1049             </script>
1050             <span class='timestamp' onclick='alertit()'>time</span>
1051             </pure-component>
1052              
1053             Usage in a view:
1054              
1055             <html lang="en">
1056             <head>
1057             <title>Title Goes Here</title>
1058             </head>
1059             <body>
1060             <div id="main">Content goes here!</div>
1061             <pure-timestamp tz='America/Chicago' />
1062             </body>
1063             </html>
1064              
1065             A component is very similar to an include or even a wrapper that you might
1066             insert with a processing instruction or via one of the other standard methods
1067             as decribed in L<Template::Pure>. The main difference is that components can
1068             bundle a style and scripting component, and components are aware of themselves
1069             in a hierarchy (for example if a component wraps other components, those inner
1070             components have the outer one as a 'parent'.
1071              
1072             Given the experimental nature of this feature, I'm going to leave it underdocumented
1073             and let you look at the source and tests for now. I'll add more when the shape of
1074             this feature is more apparent after usage.
1075              
1076             =head1 RUNTIME HOOKS
1077              
1078             This class defines the following method hooks you may optionally defined in your
1079             view subclass in order to control or otherwise influence how the view works.
1080              
1081             =head2 $class->modify_init_args($app, $args)
1082              
1083             Runs when C<COMPONENT> is called during C<setup_components>. This gets a reference
1084             to the merged arguments from all configuration. You should return this reference
1085             after modification.
1086              
1087             =head2 $self->modify_context_args($ctx, $args)
1088              
1089             Runs at C<ACCEPT_CONTEXT> and can be used to modify the arguments (including those passed to the view) before they are used to create a response. Should return C<$args>.
1090              
1091             =head1 CONFIGURATION
1092              
1093             This Catalyst Component supports the following configuation
1094              
1095             =head2 template
1096              
1097             This is a string which is an HTML Template.
1098              
1099             =head2 template_src
1100              
1101             Filesystem path where a template can be found
1102              
1103             =head2 auto_template_src
1104              
1105             Loads the template from a filesystem path based on the View name. For example if
1106             your view is "MyApp::View::Story", under $home/MyApp/View/Story.pm then you'd
1107             expect a template at $home/MyApp/View/story.html
1108              
1109             This feature is evolving and may change as the software stablizes and we get feedback
1110             from users (I know the current default location here is differnt from the way a lot
1111             of common Catalyst Views work...)
1112              
1113             =head2 returns_status
1114              
1115             An ArrayRef of HTTP status codes used to provide response helpers.
1116              
1117             =head2 directives
1118              
1119             An ArrayRef of match => actions that is used by L<Template::Pure> to apply tranformations
1120             onto a template from a given data reference.
1121              
1122             =head2 filters
1123              
1124             filters => {
1125             custom_filter => sub {
1126             my ($template, $data, @args) = @_;
1127             # Do something with the $data, possible using @args
1128             # to control what that does
1129             return $data;
1130             },
1131             },
1132              
1133             A hashref of information that is passed directly to L<Template::Pure> to be used as data
1134             filters. See L<Template::Pure/Filters>.
1135              
1136             =head2 pure_class
1137              
1138             The class used to create an instance of L<Template::Pure>. Defaults to 'Template::Pure'.
1139             You can change this if you create a custom subclass of L<Template::Pure> to use as your
1140             default template.
1141              
1142             =head1 ALSO SEE
1143              
1144             L<Catalyst>, L<Template::Pure>.
1145              
1146             L<Template::Pure> is based on a client side Javascript templating system, 'pure.js'. See
1147             L<https://beebole.com/pure/> for more information.
1148              
1149             =head1 AUTHORS & COPYRIGHT
1150              
1151             John Napiorkowski L<email:jjnapiork@cpan.org>
1152              
1153             =head1 LICENSE
1154              
1155             Copyright 2016, John Napiorkowski L<email:jjnapiork@cpan.org>
1156              
1157             This library is free software; you can redistribute it and/or modify
1158             it under the same terms as Perl itself.
1159              
1160             =cut