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