releasing V:TT 0.21
[catagits/Catalyst-View-TT.git] / lib / Catalyst / View / TT.pm
1 package Catalyst::View::TT;
2
3 use strict;
4 use base qw/Catalyst::View/;
5 use Template;
6 use Template::Timer;
7 use NEXT;
8
9 our $VERSION = '0.21';
10
11 __PACKAGE__->mk_accessors('template');
12 __PACKAGE__->mk_accessors('include_path');
13
14 =head1 NAME
15
16 Catalyst::View::TT - Template View Class
17
18 =head1 SYNOPSIS
19
20 # use the helper to create View
21     myapp_create.pl view TT TT
22
23 # configure in lib/MyApp.pm
24
25     MyApp->config({
26         name     => 'MyApp',
27         root     => MyApp->path_to('root');,
28         'V::TT' => {
29             # any TT configurations items go here
30             INCLUDE_PATH => [
31               MyApp->path_to( 'root', 'src' ), 
32               MyApp->path_to( 'root', 'lib' ), 
33             ],
34             PRE_PROCESS        => 'config/main',
35             WRAPPER            => 'site/wrapper',
36             TEMPLATE_EXTENSION => '.tt',
37
38             # two optional config items
39             CATALYST_VAR => 'Catalyst',
40             TIMER        => 1,
41         },
42     });
43          
44 # render view from lib/MyApp.pm or lib/MyApp::C::SomeController.pm
45     
46     sub message : Global {
47         my ( $self, $c ) = @_;
48         $c->stash->{template} = 'message.tt2';
49         $c->stash->{message}  = 'Hello World!';
50         $c->forward('MyApp::V::TT');
51     }
52
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
69 This is the Catalyst view class for the L<Template Toolkit|Template>.
70 Your application should defined a view class which is a subclass of
71 this module.  The easiest way to achieve this is using the
72 F<myapp_create.pl> script (where F<myapp> should be replaced with
73 whatever your application is called).  This script is created as part
74 of the Catalyst setup.
75
76     $ script/myapp_create.pl view TT TT
77
78 This creates a MyApp::V::TT.pm module in the F<lib> directory (again,
79 replacing C<MyApp> with the name of your application) which looks
80 something like this:
81
82     package FooBar::V::TT;
83     
84     use strict;
85      use base 'Catalyst::View::TT';
86
87     __PACKAGE__->config->{DEBUG} = 'all';
88
89 Now you can modify your action handlers in the main application and/or
90 controllers to forward to your view class.  You might choose to do this
91 in the end() method, for example, to automatically forward all actions
92 to the TT view class.
93
94     # In MyApp or MyApp::Controller::SomeController
95     
96     sub end : Private {
97         my( $self, $c ) = @_;
98         $c->forward('MyApp::V::TT');
99     }
100
101 =head2 CONFIGURATION
102
103 There are a three different ways to configure your view class.  The
104 first way is to call the C<config()> method in the view subclass.  This
105 happens when the module is first loaded.
106
107     package MyApp::V::TT;
108     
109     use strict;
110     use base 'Catalyst::View::TT';
111
112     MyApp::V::TT->config({
113         INCLUDE_PATH => [
114             MyApp->path_to( 'root', 'templates', 'lib' ),
115             MyApp->path_to( 'root', 'templates', 'src' ),
116         ],
117         PRE_PROCESS  => 'config/main',
118         WRAPPER      => 'site/wrapper',
119     });
120
121 The second way is to define a C<new()> method in your view subclass.
122 This performs the configuration when the view object is created,
123 shortly after being loaded.  Remember to delegate to the base class
124 C<new()> method (via C<$self-E<gt>NEXT::new()> in the example below) after
125 performing any configuration.
126
127     sub new {
128         my $self = shift;
129         $self->config({
130             INCLUDE_PATH => [
131                 MyApp->path_to( 'root', 'templates', 'lib' ),
132                 MyApp->path_to( 'root', 'templates', 'src' ),
133             ],
134             PRE_PROCESS  => 'config/main',
135             WRAPPER      => 'site/wrapper',
136         });
137         return $self->NEXT::new(@_);
138     }
139  
140 The final, and perhaps most direct way, is to define a class
141 item in your main application configuration, again by calling the
142 uniquitous C<config()> method.  The items in the class hash are
143 added to those already defined by the above two methods.  This happens
144 in the base class new() method (which is one reason why you must
145 remember to call it via C<NEXT> if you redefine the C<new()> method in a
146 subclass).
147
148     package MyApp;
149     
150     use strict;
151     use Catalyst;
152     
153     MyApp->config({
154         name     => 'MyApp',
155         root     => MyApp->path_to('root'),
156         'V::TT' => {
157             INCLUDE_PATH => [
158                 MyApp->path_to( 'root', 'templates', 'lib' ),
159                 MyApp->path_to( 'root', 'templates', 'src' ),
160             ],
161             PRE_PROCESS  => 'config/main',
162             WRAPPER      => 'site/wrapper',
163         },
164     });
165
166 Note that any configuration items defined by one of the earlier
167 methods will be overwritten by items of the same name provided by the
168 latter methods.  
169
170 =head2 DYNAMIC INCLUDE_PATH
171
172 It is sometimes needed to dynamically add additional paths to the 
173 INCLUDE_PATH variable of the template object. This can be done by setting
174 'additional_include_paths' on stash to a referrence to an array with 
175 additional paths:
176
177     $c->stash->{additional_template_paths} = [$c->config->{root} . '/test_include_path']; 
178
179 =head2 RENDERING VIEWS
180
181 The view plugin renders the template specified in the C<template>
182 item in the stash.  
183
184     sub message : Global {
185         my ( $self, $c ) = @_;
186         $c->stash->{template} = 'message.tt2';
187         $c->forward('MyApp::V::TT');
188     }
189
190 If a class item isn't defined, then it instead uses the
191 current match, as returned by C<$c-E<gt>match>.  In the above 
192 example, this would be C<message>.
193
194 The items defined in the stash are passed to the Template Toolkit for
195 use as template variables.
196
197 sub message : Global {
198     sub default : Private {
199         my ( $self, $c ) = @_;
200         $c->stash->{template} = 'message.tt2';
201         $c->stash->{message}  = 'Hello World!';
202         $c->forward('MyApp::V::TT');
203     }
204
205 A number of other template variables are also added:
206
207     c      A reference to the context object, $c
208     base   The URL base, from $c->req->base()
209     name   The application name, from $c->config->{ name }
210
211 These can be accessed from the template in the usual way:
212
213 <message.tt2>:
214
215     The message is: [% message %]
216     The base is [% base %]
217     The name is [% name %]
218
219
220 The output generated by the template is stored in
221 C<$c-E<gt>response-E<gt>output>.
222
223 =head2 TEMPLATE PROFILING
224
225 =head2 METHODS
226
227 =over 4
228
229 =item new
230
231 The constructor for the TT view. Sets up the template provider, 
232 and reads the application config.
233
234 =cut
235
236 sub _coerce_paths {
237     my ( $paths, $dlim ) = shift;
238     return () if ( !$paths );
239     return @{$paths} if ( ref $paths eq 'ARRAY' );
240
241     # tweak delim to ignore C:/
242     unless ( defined $dlim ) {
243         $dlim = ( $^O eq 'MSWin32' ) ? ':(?!\\/)' : ':';
244     }
245     return split( /$dlim/, $paths );
246 }
247
248 sub new {
249     my ( $class, $c, $arguments ) = @_;
250     my $config = {
251         EVAL_PERL          => 0,
252         TEMPLATE_EXTENSION => '',
253         %{ $class->config },
254         %{$arguments},
255     };
256     if ( ! (ref $config->{INCLUDE_PATH} eq 'ARRAY') ) {
257         my $delim = $config->{DELIMITER};
258         my @include_path
259             = _coerce_paths( $config->{INCLUDE_PATH}, $delim );
260         if ( !@include_path ) {
261             my $root = $c->config->{root};
262             my $base = Path::Class::dir( $root, 'base' );
263             @include_path = ( "$root", "$base" );
264         }
265         $config->{INCLUDE_PATH} = \@include_path;
266     }
267
268
269
270     # if we're debugging and/or the TIMER option is set, then we install
271     # Template::Timer as a custom CONTEXT object, but only if we haven't
272     # already got a custom CONTEXT defined
273
274     if ( $config->{TIMER} ) {
275         if ( $config->{CONTEXT} ) {
276             $c->log->error(
277                 'Cannot use Template::Timer - a TT CONFIG is already defined'
278             );
279         }
280         else {
281             $config->{CONTEXT} = Template::Timer->new(%$config);
282         }
283     }
284
285     if ( $c->debug && $config->{DUMP_CONFIG} ) {
286         use Data::Dumper;
287         $c->log->debug( "TT Config: ", Dumper($config) );
288     }
289     if ( $config->{PROVIDERS} ) {
290         my @providers = ();
291         if ( ref($config->{PROVIDERS}) eq 'ARRAY') {
292             foreach my $p (@{$config->{PROVIDERS}}) {
293                 my $pname = $p->{name};
294                 eval "require Template::Provider::$pname";
295                 if(!$@) {
296                     push @providers, "Template::Provider::${pname}"->new($p->{args});
297                 }
298             }
299         }
300         delete $config->{PROVIDERS};
301         if(@providers) {
302             $config->{LOAD_TEMPLATES} = \@providers;
303         }
304     }
305
306     my $self = $class->NEXT::new(
307         $c,
308         {   template => Template->new($config) || do {
309                 my $error = Template->error();
310                 $c->log->error($error);
311                 $c->error($error);
312                 return undef;
313             },
314             %{$config},
315         },
316     );
317     $self->include_path($config->{INCLUDE_PATH});
318     $self->config($config);
319
320     return $self;
321 }
322
323 =item process
324
325 Renders the template specified in C<$c-E<gt>stash-E<gt>{template}> or
326 C<$c-E<gt>request-E<gt>match>. Template variables are set up from the
327 contents of C<$c-E<gt>stash>, augmented with C<base> set to
328 C<$c-E<gt>req-E<gt>base>, C<c> to C<$c> and C<name> to
329 C<$c-E<gt>config-E<gt>{name}>. Alternately, the C<CATALYST_VAR>
330 configuration item can be defined to specify the name of a template
331 variable through which the context reference (C<$c>) can be accessed.
332 In this case, the C<c>, C<base> and C<name> variables are omitted.
333 Output is stored in C<$c-E<gt>response-E<gt>output>.
334
335 =cut
336
337 sub process {
338     my ( $self, $c ) = @_;
339
340     my $template = $c->stash->{template}
341       || ( $c->request->match || $c->request->action )
342       . $self->config->{TEMPLATE_EXTENSION};
343
344     unless ($template) {
345         $c->log->debug('No template specified for rendering') if $c->debug;
346         return 0;
347     }
348
349     $c->log->debug(qq/Rendering template "$template"/) if $c->debug;
350
351     my $output;
352     my $vars = { $self->template_vars($c) };
353
354     unshift @{ $self->include_path },
355       @{ $c->stash->{additional_template_paths} }
356       if ref $c->stash->{additional_template_paths};
357     unless ( $self->template->process( $template, $vars, \$output ) ) {
358         my $error = $self->template->error;
359         $error = qq/Couldn't render template "$error"/;
360         $c->log->error($error);
361         $c->error($error);
362         return 0;
363     }
364     splice @{ $self->include_path }, 0,
365       scalar @{ $c->stash->{additional_template_paths} }
366       if ref $c->stash->{additional_template_paths};
367
368     unless ( $c->response->content_type ) {
369         $c->response->content_type('text/html; charset=utf-8');
370     }
371
372     $c->response->body($output);
373
374     return 1;
375 }
376
377 =item template_vars
378
379 Returns a list of keys/values to be used as the variables in the
380 template.
381
382 =cut
383
384 sub template_vars {
385     my ( $self, $c ) = @_;
386
387     my $cvar = $self->config->{CATALYST_VAR};
388
389     defined $cvar
390       ? ( $cvar => $c )
391       : (
392         c    => $c,
393         base => $c->req->base,
394         name => $c->config->{name}
395       ),
396       %{ $c->stash() }
397
398 }
399 =item config
400
401 This method allows your view subclass to pass additional settings to
402 the TT configuration hash, or to set the options as below:
403
404 =over 2
405
406 =item C<CATALYST_VAR> 
407
408 Allows you to change the name of the Catalyst context object. If set, it will also
409 remove the base and name aliases, so you will have access them through <context>.
410
411 For example:
412
413     MyApp->config({
414         name     => 'MyApp',
415         root     => MyApp->path_to('root'),
416         'V::TT' => {
417             CATALYST_VAR => 'Catalyst',
418         },
419     });
420
421 F<message.tt2>:
422
423     The base is [% Catalyst.req.base %]
424     The name is [% Catalyst.config.name %]
425
426 =item C<TIMER>
427
428 If you have configured Catalyst for debug output, and turned on the TIMER setting,
429 C<Catalyst::View::TT> will enable profiling of template processing
430 (using L<Template::Timer>). This will embed HTML comments in the
431 output from your templates, such as:
432
433     <!-- TIMER START: process mainmenu/mainmenu.ttml -->
434     <!-- TIMER START: include mainmenu/cssindex.tt -->
435     <!-- TIMER START: process mainmenu/cssindex.tt -->
436     <!-- TIMER END: process mainmenu/cssindex.tt (0.017279 seconds) -->
437     <!-- TIMER END: include mainmenu/cssindex.tt (0.017401 seconds) -->
438
439     ....
440
441     <!-- TIMER END: process mainmenu/footer.tt (0.003016 seconds) -->
442
443
444 =item C<TEMPLATE_EXTENSION>
445
446 a sufix to add when looking for templates bases on the C<match> method in L<Catalyst::Request>.
447
448 For example:
449
450   package MyApp::C::Test;
451   sub test : Local { .. } 
452
453 Would by default look for a template in <root>/test/test. If you set TEMPLATE_EXTENSION to '.tt', it will look for
454 <root>/test/test.tt.
455
456 =back
457
458 =back
459
460 =head2 HELPERS
461
462 The L<Catalyst::Helper::View::TT> and
463 L<Catalyst::Helper::View::TTSite> helper modules are provided to create
464 your view module.  There are invoked by the F<myapp_create.pl> script:
465
466     $ script/myapp_create.pl view TT TT
467
468     $ script/myapp_create.pl view TT TTSite
469
470 The L<Catalyst::Helper::View::TT> module creates a basic TT view
471 module.  The L<Catalyst::Helper::View::TTSite> module goes a little
472 further.  It also creates a default set of templates to get you
473 started.  It also configures the view module to locate the templates
474 automatically.
475
476 =head1 SEE ALSO
477
478 L<Catalyst>, L<Catalyst::Helper::View::TT>,
479 L<Catalyst::Helper::View::TTSite>, L<Template::Manual>
480
481 =head1 AUTHORS
482
483 Sebastian Riedel, C<sri@cpan.org>
484
485 Marcus Ramberg, C<mramberg@cpan.org>
486
487 Jesse Sheidlower, C<jester@panix.com>
488
489 Andy Wardley, C<abw@cpan.org>
490
491 =head1 COPYRIGHT
492
493 This program is free software, you can redistribute it and/or modify it 
494 under the same terms as Perl itself.
495
496 =cut
497
498 1;