Commit | Line | Data |
fdf47cd1 |
1 | package Catalyst::View::TT; |
2 | |
3 | use strict; |
4 | use warnings; |
5 | |
6 | use base qw/Catalyst::View/; |
7 | use Data::Dump 'dump'; |
8 | use Template; |
9 | use Template::Timer; |
10 | use MRO::Compat; |
11 | |
12 | our $VERSION = '0.30'; |
13 | |
14 | __PACKAGE__->mk_accessors('template'); |
15 | __PACKAGE__->mk_accessors('include_path'); |
16 | |
17 | *paths = \&include_path; |
18 | |
19 | =head1 NAME |
20 | |
21 | Catalyst::View::TT - Template View Class |
22 | |
23 | =head1 SYNOPSIS |
24 | |
25 | # use the helper to create your View |
26 | |
27 | myapp_create.pl view TT TT |
28 | |
29 | # configure in lib/MyApp.pm (Could be set from configfile instead) |
30 | |
31 | MyApp->config( |
32 | name => 'MyApp', |
33 | root => MyApp->path_to('root'), |
34 | 'View::TT' => { |
35 | # any TT configurations items go here |
36 | INCLUDE_PATH => [ |
37 | MyApp->path_to( 'root', 'src' ), |
38 | MyApp->path_to( 'root', 'lib' ), |
39 | ], |
40 | TEMPLATE_EXTENSION => '.tt', |
41 | CATALYST_VAR => 'c', |
42 | TIMER => 0, |
43 | # Not set by default |
44 | PRE_PROCESS => 'config/main', |
45 | WRAPPER => 'site/wrapper', |
46 | }, |
47 | ); |
48 | |
49 | # render view from lib/MyApp.pm or lib/MyApp::Controller::SomeController.pm |
50 | |
51 | sub message : Global { |
52 | my ( $self, $c ) = @_; |
53 | $c->stash->{template} = 'message.tt2'; |
54 | $c->stash->{message} = 'Hello World!'; |
55 | $c->forward( $c->view('TT') ); |
56 | } |
57 | |
58 | # access variables from template |
59 | |
60 | The message is: [% message %]. |
61 | |
62 | # example when CATALYST_VAR is set to 'Catalyst' |
63 | Context is [% Catalyst %] |
64 | The base is [% Catalyst.req.base %] |
65 | The name is [% Catalyst.config.name %] |
66 | |
67 | # example when CATALYST_VAR isn't set |
68 | Context is [% c %] |
69 | The base is [% base %] |
70 | The name is [% name %] |
71 | |
72 | =cut |
73 | |
74 | sub _coerce_paths { |
75 | my ( $paths, $dlim ) = shift; |
76 | return () if ( !$paths ); |
77 | return @{$paths} if ( ref $paths eq 'ARRAY' ); |
78 | |
79 | # tweak delim to ignore C:/ |
80 | unless ( defined $dlim ) { |
81 | $dlim = ( $^O eq 'MSWin32' ) ? ':(?!\\/)' : ':'; |
82 | } |
83 | return split( /$dlim/, $paths ); |
84 | } |
85 | |
86 | sub new { |
87 | my ( $class, $c, $arguments ) = @_; |
88 | my $config = { |
89 | EVAL_PERL => 0, |
90 | TEMPLATE_EXTENSION => '', |
91 | %{ $class->config }, |
92 | %{$arguments}, |
93 | }; |
94 | if ( ! (ref $config->{INCLUDE_PATH} eq 'ARRAY') ) { |
95 | my $delim = $config->{DELIMITER}; |
96 | my @include_path |
97 | = _coerce_paths( $config->{INCLUDE_PATH}, $delim ); |
98 | if ( !@include_path ) { |
99 | my $root = $c->config->{root}; |
100 | my $base = Path::Class::dir( $root, 'base' ); |
101 | @include_path = ( "$root", "$base" ); |
102 | } |
103 | $config->{INCLUDE_PATH} = \@include_path; |
104 | } |
105 | |
106 | # if we're debugging and/or the TIMER option is set, then we install |
107 | # Template::Timer as a custom CONTEXT object, but only if we haven't |
108 | # already got a custom CONTEXT defined |
109 | |
110 | if ( $config->{TIMER} ) { |
111 | if ( $config->{CONTEXT} ) { |
112 | $c->log->error( |
113 | 'Cannot use Template::Timer - a TT CONTEXT is already defined' |
114 | ); |
115 | } |
116 | else { |
117 | $config->{CONTEXT} = Template::Timer->new(%$config); |
118 | } |
119 | } |
120 | |
121 | if ( $c->debug && $config->{DUMP_CONFIG} ) { |
122 | $c->log->debug( "TT Config: ", dump($config) ); |
123 | } |
124 | |
125 | my $self = $class->next::method( |
126 | $c, { %$config }, |
127 | ); |
128 | |
129 | # Set base include paths. Local'd in render if needed |
130 | $self->include_path($config->{INCLUDE_PATH}); |
131 | |
132 | $self->config($config); |
133 | |
134 | # Creation of template outside of call to new so that we can pass [ $self ] |
135 | # as INCLUDE_PATH config item, which then gets ->paths() called to get list |
136 | # of include paths to search for templates. |
137 | |
138 | # Use a weakend copy of self so we dont have loops preventing GC from working |
139 | my $copy = $self; |
140 | Scalar::Util::weaken($copy); |
141 | $config->{INCLUDE_PATH} = [ sub { $copy->paths } ]; |
142 | |
143 | if ( $config->{PROVIDERS} ) { |
144 | my @providers = (); |
145 | if ( ref($config->{PROVIDERS}) eq 'ARRAY') { |
146 | foreach my $p (@{$config->{PROVIDERS}}) { |
147 | my $pname = $p->{name}; |
148 | my $prov = 'Template::Provider'; |
149 | if($pname eq '_file_') |
150 | { |
151 | $p->{args} = { %$config }; |
152 | } |
153 | else |
154 | { |
155 | if($pname =~ s/^\+//) { |
156 | $prov = $pname; |
157 | } |
158 | else |
159 | { |
160 | $prov .= "::$pname"; |
161 | } |
162 | # We copy the args people want from the config |
163 | # to the args |
164 | $p->{args} ||= {}; |
165 | if ($p->{copy_config}) { |
166 | map { $p->{args}->{$_} = $config->{$_} } |
167 | grep { exists $config->{$_} } |
168 | @{ $p->{copy_config} }; |
169 | } |
170 | } |
171 | eval "require $prov"; |
172 | if(!$@) { |
173 | push @providers, "$prov"->new($p->{args}); |
174 | } |
175 | else |
176 | { |
177 | $c->log->warn("Can't load $prov, ($@)"); |
178 | } |
179 | } |
180 | } |
181 | delete $config->{PROVIDERS}; |
182 | if(@providers) { |
183 | $config->{LOAD_TEMPLATES} = \@providers; |
184 | } |
185 | } |
186 | |
187 | $self->{template} = |
188 | Template->new($config) || do { |
189 | my $error = Template->error(); |
190 | $c->log->error($error); |
191 | $c->error($error); |
192 | return undef; |
193 | }; |
194 | |
195 | |
196 | return $self; |
197 | } |
198 | |
199 | sub process { |
200 | my ( $self, $c ) = @_; |
201 | |
202 | my $template = $c->stash->{template} |
203 | || $c->action . $self->config->{TEMPLATE_EXTENSION}; |
204 | |
205 | unless (defined $template) { |
206 | $c->log->debug('No template specified for rendering') if $c->debug; |
207 | return 0; |
208 | } |
209 | |
210 | my $output = $self->render($c, $template); |
211 | |
212 | if (UNIVERSAL::isa($output, 'Template::Exception')) { |
213 | my $error = qq/Couldn't render template "$output"/; |
214 | $c->log->error($error); |
215 | $c->error($error); |
216 | return 0; |
217 | } |
218 | |
219 | unless ( $c->response->content_type ) { |
220 | $c->response->content_type('text/html; charset=utf-8'); |
221 | } |
222 | |
223 | $c->response->body($output); |
224 | |
225 | return 1; |
226 | } |
227 | |
228 | sub render { |
229 | my ($self, $c, $template, $args) = @_; |
230 | |
f42c8153 |
231 | $c->log->debug(qq/Rendering template "$template"/) if $c && $c->debug; |
fdf47cd1 |
232 | |
233 | my $output; |
234 | my $vars = { |
235 | (ref $args eq 'HASH' ? %$args : %{ $c->stash() }), |
236 | $self->template_vars($c) |
237 | }; |
238 | |
239 | local $self->{include_path} = |
240 | [ @{ $vars->{additional_template_paths} }, @{ $self->{include_path} } ] |
241 | if ref $vars->{additional_template_paths}; |
242 | |
243 | unless ($self->template->process( $template, $vars, \$output ) ) { |
244 | return $self->template->error; |
245 | } else { |
246 | return $output; |
247 | } |
248 | } |
249 | |
250 | sub template_vars { |
251 | my ( $self, $c ) = @_; |
252 | |
f42c8153 |
253 | return () unless $c; |
fdf47cd1 |
254 | my $cvar = $self->config->{CATALYST_VAR}; |
255 | |
256 | defined $cvar |
257 | ? ( $cvar => $c ) |
258 | : ( |
259 | c => $c, |
260 | base => $c->req->base, |
261 | name => $c->config->{name} |
262 | ) |
263 | } |
264 | |
265 | |
266 | 1; |
267 | |
268 | __END__ |
269 | |
270 | =head1 DESCRIPTION |
271 | |
272 | This is the Catalyst view class for the L<Template Toolkit|Template>. |
273 | Your application should defined a view class which is a subclass of |
274 | this module. The easiest way to achieve this is using the |
275 | F<myapp_create.pl> script (where F<myapp> should be replaced with |
276 | whatever your application is called). This script is created as part |
277 | of the Catalyst setup. |
278 | |
279 | $ script/myapp_create.pl view TT TT |
280 | |
281 | This creates a MyApp::View::TT.pm module in the F<lib> directory (again, |
282 | replacing C<MyApp> with the name of your application) which looks |
283 | something like this: |
284 | |
285 | package FooBar::View::TT; |
286 | |
287 | use strict; |
288 | use warnings; |
289 | |
290 | use base 'Catalyst::View::TT'; |
291 | |
292 | __PACKAGE__->config->{DEBUG} = 'all'; |
293 | |
294 | Now you can modify your action handlers in the main application and/or |
295 | controllers to forward to your view class. You might choose to do this |
296 | in the end() method, for example, to automatically forward all actions |
297 | to the TT view class. |
298 | |
299 | # In MyApp or MyApp::Controller::SomeController |
300 | |
301 | sub end : Private { |
302 | my( $self, $c ) = @_; |
303 | $c->forward( $c->view('TT') ); |
304 | } |
305 | |
306 | =head2 CONFIGURATION |
307 | |
308 | There are a three different ways to configure your view class. The |
309 | first way is to call the C<config()> method in the view subclass. This |
310 | happens when the module is first loaded. |
311 | |
312 | package MyApp::View::TT; |
313 | |
314 | use strict; |
315 | use base 'Catalyst::View::TT'; |
316 | |
317 | MyApp::View::TT->config({ |
318 | INCLUDE_PATH => [ |
319 | MyApp->path_to( 'root', 'templates', 'lib' ), |
320 | MyApp->path_to( 'root', 'templates', 'src' ), |
321 | ], |
322 | PRE_PROCESS => 'config/main', |
323 | WRAPPER => 'site/wrapper', |
324 | }); |
325 | |
326 | The second way is to define a C<new()> method in your view subclass. |
327 | This performs the configuration when the view object is created, |
328 | shortly after being loaded. Remember to delegate to the base class |
329 | C<new()> method (via C<$self-E<gt>next::method()> in the example below) after |
330 | performing any configuration. |
331 | |
332 | sub new { |
333 | my $self = shift; |
334 | $self->config({ |
335 | INCLUDE_PATH => [ |
336 | MyApp->path_to( 'root', 'templates', 'lib' ), |
337 | MyApp->path_to( 'root', 'templates', 'src' ), |
338 | ], |
339 | PRE_PROCESS => 'config/main', |
340 | WRAPPER => 'site/wrapper', |
341 | }); |
342 | return $self->next::method(@_); |
343 | } |
344 | |
345 | The final, and perhaps most direct way, is to define a class |
346 | item in your main application configuration, again by calling the |
347 | ubiquitous C<config()> method. The items in the class hash are |
348 | added to those already defined by the above two methods. This happens |
349 | in the base class new() method (which is one reason why you must |
350 | remember to call it via C<MRO::Compat> if you redefine the C<new()> |
351 | method in a subclass). |
352 | |
353 | package MyApp; |
354 | |
355 | use strict; |
356 | use Catalyst; |
357 | |
358 | MyApp->config({ |
359 | name => 'MyApp', |
360 | root => MyApp->path_to('root'), |
361 | 'View::TT' => { |
362 | INCLUDE_PATH => [ |
363 | MyApp->path_to( 'root', 'templates', 'lib' ), |
364 | MyApp->path_to( 'root', 'templates', 'src' ), |
365 | ], |
366 | PRE_PROCESS => 'config/main', |
367 | WRAPPER => 'site/wrapper', |
368 | }, |
369 | }); |
370 | |
371 | Note that any configuration items defined by one of the earlier |
372 | methods will be overwritten by items of the same name provided by the |
373 | latter methods. |
374 | |
375 | =head2 DYNAMIC INCLUDE_PATH |
376 | |
377 | Sometimes it is desirable to modify INCLUDE_PATH for your templates at run time. |
378 | |
379 | Additional paths can be added to the start of INCLUDE_PATH via the stash as |
380 | follows: |
381 | |
382 | $c->stash->{additional_template_paths} = |
383 | [$c->config->{root} . '/test_include_path']; |
384 | |
385 | If you need to add paths to the end of INCLUDE_PATH, there is also an |
386 | include_path() accessor available: |
387 | |
388 | push( @{ $c->view('TT')->include_path }, qw/path/ ); |
389 | |
390 | Note that if you use include_path() to add extra paths to INCLUDE_PATH, you |
391 | MUST check for duplicate paths. Without such checking, the above code will add |
392 | "path" to INCLUDE_PATH at every request, causing a memory leak. |
393 | |
394 | A safer approach is to use include_path() to overwrite the array of paths |
395 | rather than adding to it. This eliminates both the need to perform duplicate |
396 | checking and the chance of a memory leak: |
397 | |
398 | @{ $c->view('TT')->include_path } = qw/path another_path/; |
399 | |
400 | If you are calling C<render> directly then you can specify dynamic paths by |
401 | having a C<additional_template_paths> key with a value of additonal directories |
402 | to search. See L<CAPTURING TEMPLATE OUTPUT> for an example showing this. |
403 | |
404 | =head2 RENDERING VIEWS |
405 | |
406 | The view plugin renders the template specified in the C<template> |
407 | item in the stash. |
408 | |
409 | sub message : Global { |
410 | my ( $self, $c ) = @_; |
411 | $c->stash->{template} = 'message.tt2'; |
412 | $c->forward( $c->view('TT') ); |
413 | } |
414 | |
415 | If a stash item isn't defined, then it instead uses the |
416 | stringification of the action dispatched to (as defined by $c->action) |
417 | in the above example, this would be C<message>, but because the default |
418 | is to append '.tt', it would load C<root/message.tt>. |
419 | |
420 | The items defined in the stash are passed to the Template Toolkit for |
421 | use as template variables. |
422 | |
423 | sub default : Private { |
424 | my ( $self, $c ) = @_; |
425 | $c->stash->{template} = 'message.tt2'; |
426 | $c->stash->{message} = 'Hello World!'; |
427 | $c->forward( $c->view('TT') ); |
428 | } |
429 | |
430 | A number of other template variables are also added: |
431 | |
432 | c A reference to the context object, $c |
433 | base The URL base, from $c->req->base() |
434 | name The application name, from $c->config->{ name } |
435 | |
436 | These can be accessed from the template in the usual way: |
437 | |
438 | <message.tt2>: |
439 | |
440 | The message is: [% message %] |
441 | The base is [% base %] |
442 | The name is [% name %] |
443 | |
444 | |
445 | The output generated by the template is stored in C<< $c->response->body >>. |
446 | |
447 | =head2 CAPTURING TEMPLATE OUTPUT |
448 | |
449 | If you wish to use the output of a template for some other purpose than |
450 | displaying in the response, e.g. for sending an email, this is possible using |
451 | L<Catalyst::Plugin::Email> and the L<render> method: |
452 | |
453 | sub send_email : Local { |
454 | my ($self, $c) = @_; |
455 | |
456 | $c->email( |
457 | header => [ |
458 | To => 'me@localhost', |
459 | Subject => 'A TT Email', |
460 | ], |
461 | body => $c->view('TT')->render($c, 'email.tt', { |
462 | additional_template_paths => [ $c->config->{root} . '/email_templates'], |
463 | email_tmpl_param1 => 'foo' |
464 | } |
465 | ), |
466 | ); |
467 | # Redirect or display a message |
468 | } |
469 | |
470 | =head2 TEMPLATE PROFILING |
471 | |
472 | See L<C<TIMER>> property of the L<config> method. |
473 | |
474 | =head2 METHODS |
475 | |
476 | =head2 new |
477 | |
478 | The constructor for the TT view. Sets up the template provider, |
479 | and reads the application config. |
480 | |
481 | =head2 process |
482 | |
483 | Renders the template specified in C<< $c->stash->{template} >> or |
484 | C<< $c->action >> (the private name of the matched action). Calls L<render> to |
485 | perform actual rendering. Output is stored in C<< $c->response->body >>. |
486 | |
487 | =head2 render($c, $template, \%args) |
488 | |
489 | Renders the given template and returns output, or a L<Template::Exception> |
490 | object upon error. |
491 | |
492 | The template variables are set to C<%$args> if $args is a hashref, or |
493 | $C<< $c->stash >> otherwise. In either case the variables are augmented with |
494 | C<base> set to C< << $c->req->base >>, C<c> to C<$c> and C<name> to |
495 | C<< $c->config->{name} >>. Alternately, the C<CATALYST_VAR> configuration item |
496 | can be defined to specify the name of a template variable through which the |
497 | context reference (C<$c>) can be accessed. In this case, the C<c>, C<base> and |
498 | C<name> variables are omitted. |
499 | |
500 | C<$template> can be anything that Template::process understands how to |
501 | process, including the name of a template file or a reference to a test string. |
502 | See L<Template::process|Template/process> for a full list of supported formats. |
503 | |
f42c8153 |
504 | To use the render method outside of your Catalyst app, just pass a undef context. |
505 | This can be useful for tests, for instance. |
506 | |
fdf47cd1 |
507 | =head2 template_vars |
508 | |
509 | Returns a list of keys/values to be used as the catalyst variables in the |
510 | template. |
511 | |
512 | =head2 config |
513 | |
514 | This method allows your view subclass to pass additional settings to |
515 | the TT configuration hash, or to set the options as below: |
516 | |
517 | =head2 paths |
518 | |
519 | The list of paths TT will look for templates in. |
520 | |
521 | =head2 C<CATALYST_VAR> |
522 | |
523 | Allows you to change the name of the Catalyst context object. If set, it will also |
524 | remove the base and name aliases, so you will have access them through <context>. |
525 | |
526 | For example: |
527 | |
528 | MyApp->config({ |
529 | name => 'MyApp', |
530 | root => MyApp->path_to('root'), |
531 | 'View::TT' => { |
532 | CATALYST_VAR => 'Catalyst', |
533 | }, |
534 | }); |
535 | |
536 | F<message.tt2>: |
537 | |
538 | The base is [% Catalyst.req.base %] |
539 | The name is [% Catalyst.config.name %] |
540 | |
541 | =head2 C<TIMER> |
542 | |
543 | If you have configured Catalyst for debug output, and turned on the TIMER setting, |
544 | C<Catalyst::View::TT> will enable profiling of template processing |
545 | (using L<Template::Timer>). This will embed HTML comments in the |
546 | output from your templates, such as: |
547 | |
548 | <!-- TIMER START: process mainmenu/mainmenu.ttml --> |
549 | <!-- TIMER START: include mainmenu/cssindex.tt --> |
550 | <!-- TIMER START: process mainmenu/cssindex.tt --> |
551 | <!-- TIMER END: process mainmenu/cssindex.tt (0.017279 seconds) --> |
552 | <!-- TIMER END: include mainmenu/cssindex.tt (0.017401 seconds) --> |
553 | |
554 | .... |
555 | |
556 | <!-- TIMER END: process mainmenu/footer.tt (0.003016 seconds) --> |
557 | |
558 | |
559 | =head2 C<TEMPLATE_EXTENSION> |
560 | |
561 | a sufix to add when looking for templates bases on the C<match> method in L<Catalyst::Request>. |
562 | |
563 | For example: |
564 | |
565 | package MyApp::Controller::Test; |
566 | sub test : Local { .. } |
567 | |
568 | Would by default look for a template in <root>/test/test. If you set TEMPLATE_EXTENSION to '.tt', it will look for |
569 | <root>/test/test.tt. |
570 | |
571 | =head2 C<PROVIDERS> |
572 | |
573 | Allows you to specify the template providers that TT will use. |
574 | |
575 | MyApp->config({ |
576 | name => 'MyApp', |
577 | root => MyApp->path_to('root'), |
578 | 'View::TT' => { |
579 | PROVIDERS => [ |
580 | { |
581 | name => 'DBI', |
582 | args => { |
583 | DBI_DSN => 'dbi:DB2:books', |
584 | DBI_USER=> 'foo' |
585 | } |
586 | }, { |
587 | name => '_file_', |
588 | args => {} |
589 | } |
590 | ] |
591 | }, |
592 | }); |
593 | |
594 | The 'name' key should correspond to the class name of the provider you |
595 | want to use. The _file_ name is a special case that represents the default |
596 | TT file-based provider. By default the name is will be prefixed with |
597 | 'Template::Provider::'. You can fully qualify the name by using a unary |
598 | plus: |
599 | |
600 | name => '+MyApp::Provider::Foo' |
601 | |
602 | You can also specify the 'copy_config' key as an arrayref, to copy those keys |
603 | from the general config, into the config for the provider: |
604 | |
605 | DEFAULT_ENCODING => 'utf-8', |
606 | PROVIDERS => [ |
607 | { |
608 | name => 'Encoding', |
609 | copy_config => [qw(DEFAULT_ENCODING INCLUDE_PATH)] |
610 | } |
611 | ] |
612 | |
613 | This can prove useful when you want to use the additional_template_paths hack |
614 | in your own provider, or if you need to use Template::Provider::Encoding |
615 | |
616 | =head2 HELPERS |
617 | |
618 | The L<Catalyst::Helper::View::TT> and |
619 | L<Catalyst::Helper::View::TTSite> helper modules are provided to create |
620 | your view module. There are invoked by the F<myapp_create.pl> script: |
621 | |
622 | $ script/myapp_create.pl view TT TT |
623 | |
624 | $ script/myapp_create.pl view TT TTSite |
625 | |
626 | The L<Catalyst::Helper::View::TT> module creates a basic TT view |
627 | module. The L<Catalyst::Helper::View::TTSite> module goes a little |
628 | further. It also creates a default set of templates to get you |
629 | started. It also configures the view module to locate the templates |
630 | automatically. |
631 | |
e1f17660 |
632 | =head1 NOTES |
633 | |
634 | If you are using the L<CGI> module inside your templates, you will |
635 | experience that the Catalyst server appears to hang while rendering |
636 | the web page. This is due to the debug mode of L<CGI> (which is |
637 | waiting for input in the terminal window). Turning off the |
638 | debug mode using the "-no_debug" option solves the |
639 | problem, eg.: |
640 | |
641 | [% USE CGI('-no_debug') %] |
642 | |
fdf47cd1 |
643 | =head1 SEE ALSO |
644 | |
645 | L<Catalyst>, L<Catalyst::Helper::View::TT>, |
646 | L<Catalyst::Helper::View::TTSite>, L<Template::Manual> |
647 | |
648 | =head1 AUTHORS |
649 | |
650 | Sebastian Riedel, C<sri@cpan.org> |
651 | |
652 | Marcus Ramberg, C<mramberg@cpan.org> |
653 | |
654 | Jesse Sheidlower, C<jester@panix.com> |
655 | |
656 | Andy Wardley, C<abw@cpan.org> |
657 | |
658 | =head1 COPYRIGHT |
659 | |
660 | This program is free software. You can redistribute it and/or modify it |
661 | under the same terms as Perl itself. |
662 | |
663 | =cut |