File Coverage

blib/lib/Catalyst/View/Template/Pure.pm
Criterion Covered Total %
statement 149 177 84.1
branch 37 64 57.8
condition 3 6 50.0
subroutine 25 34 73.5
pod 7 21 33.3
total 221 302 73.1


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