Author image Αριστοτέλης Παγκαλτζής

NAME

Catalyst::View::Template - Template Toolkit meets Catalyst

SYNOPSIS

 package MyApp::View::Web;
 use parent 'Catalyst::View::Template';
 1;
 
 package MyApp;
 __PACKAGE__->config( default_view => 'Web' );
 1;
 
 package MyApp::Controller::Root;
 __PACKAGE__->config( namespace => '' );
 sub end : ActionClass('RenderView') {}
 1;

DESCRIPTION

This is a Catalyst view class for the Template Toolkit with the following design objectives:

  • To be as close as possible to direct use of Template Toolkit

  • To nevertheless integrate seamlessly with Catalyst

  • To make all parts of its behaviour easy to augment

    Taken together with the other objectives, this should make it easy to repurpose for any view class in which you might want to use Template Toolkit templates.

This is a rethink of Catalyst::View::TT whose focus is on providing lots of Catalyst-specific features on top of Template Toolkit, which ultimately only make templates harder to reuse or test in other contexts.

DEFAULT OPERATION

As with any Catalyst view, you create a subclass of it for your application, something like MyApp::View::Web. Generally you then forward to View::Web from the end action of your root controller, which indirectly invokes its "process" method:

 package MyApp::Controller::Root;
 
 __PACKAGE__->config( namespace => '' );
 
 # ...
 
 sub end : Private {
   my ( $self, $c ) = ( shift, @_ );
   $c->forward( $c->view( 'Web' ) ) # just $c->view if set as default_view
     if  $c->req->method ne 'HEAD'
     and $c->response->status !~ /^20[14]$|^3[0-9][0-9]$/
     and ( not defined $c->response->body )
     and ( not @{ $c->error } );
 }
 
 1;

This picks an appropriate template to render for the request, using the data you put into the $c->stash as template variables, then sets $c->response->content_type and $c->response->body.

The name of the template processed corresponds to the private path of the dispatched action, unless you specify a template explicitly using the template stash key. The value of "template_ext" is appended to the template name before calling Template Toolkit to process it, which will look in INCLUDE_PATH to find the corresponding file.

So if you consider the lobster action in a controller called Essay:

 package MyApp::Controller::Essay;
 sub lobster : Local {
   my ( $self, $c ) = ( shift, @_ );
   # ...
 }

This will be invoked when you go to http://localhost:5000/essay/lobster. The private path of this action is essay/lobster. If "template_ext" is set to .tt2 and INCLUDE_PATH is left unchanged, that means the template that will be rendered is root/essay/lobster.tt2.

To set "template_ext" or other configuratin options you can use the usual mechanisms available in Catalyst:

  1. Calling the config class method in the view class:

     package MyApp::View::Web;
     use parent 'Catalyst::View::Template';
     
     __PACKAGE__->config(
       template_ext => '.tt',
       PRE_PROCESS  => 'config/main',
       WRAPPER      => 'site/wrapper',
     );
     
     1;
  2. Calling the config class method in the application class and passing it a configuration section named after the view class:

     package MyApp;
     
     # ...
     
     __PACKAGE__->config(
       'View::Web' => {
         INCLUDE_PATH => [
           __PACKAGE__->path_to( 'root', 'src' ),
           __PACKAGE__->path_to( 'root', 'lib' ),
         ],
       },
     );
     
     1;
  3. Calling the config class method in the application class indirectly, through a plugin such as Catalyst::Plugin::ConfigLoader, and putting the configuration section into a configuration file.

CONFIGURATION

By default, Catalyst::View::Template instantiates Template Toolkit as follows:

 Template->new( {
   EVAL_PERL    => 0,
   ENCODING     => 'UTF-8',
   INCLUDE_PATH => [ $c->path_to( 'root' ) ],
 } )

You can override any or all of these settings or add others by passing any of the Template Toolkit configuration options through the configuration of your view class. You can also override "new_template" for control over the final set of configuration values.

The following non-Template configuration options are also available:

content_type

The content type which will be set on the response, unless one has been set already.

Defaults to text/html; charset=utf-8

template_ext

A suffix to add to the template name just before passing it to "process" in Template.

Defaults to the empty string.

class_name

The template class to instantiate. E.g. for easier XSS protection:

 package MyApp::View::Web;
 use parent 'Catalyst::View::Template';
 __PACKAGE__->config(
   class_name  => 'Template::AutoFilter',
   AUTO_FILTER => 'html',
 );

Defaults to Template.

METHODS

This class inherits everything in Catalyst::Component.

Additionally it implements the following methods:

new_template

 $view->new_template( $c, \%config )

This is called by the constructor to construct the Template instance. It gets passed the configuration hash to pass to "new" in Template and is expected to return an instance of Template or something like it. It throws an exception on failure.

You might want to override this method in your view class to modify the result of merging all static class configuration. Most likely you might use this to inject values into the INCLUDE_PATH in \%config and then pass through to the super method. But you could also construct an instance yourself, either to completely bypass all defaults (including "class_name"), or maybe to not throw an exception on error.

process

 $view->process( $c )

This decides which template to call "render" on, then calls it with a copy of the $c->stash. The template name is taken from $c->action->reverse by default, or from $c->stash->{'template'} if that has been set.

On success it then calls "process_output" on the output. If template execution fails it calls "process_error". It returns whatever the called method returns.

You might want to override this method in your view class if you want additional steps taken when the view is forwarded to, but which should not be taken when "render" is called directly.

render

 $view->render( $c, $template, \%vars, \$output )
 $view->render( $c, $template, \%vars, \$output, ... )

This renders the template named by $template and the "template_ext" configuration option into $output, passing it the values from \%vars, and returns true on success. In other words, the default implementation means it is a shorthand for this:

 $view->template->process( $template . $view->template_ext, \%vars, \$output )

As usual, you can also forward to this method:

 $c->forward( 'View::Web', 'render', $template, \%vars, \$output )

You might want to override this method in your view class if you always want certain additional steps taken before rendering a template. E.g. you would use this to add a standard set of variables to the variables passed to any template, in which case you will want to modify \%vars and then pass through to the super method.

E.g. to mimic the standard Catalyst::View::TT behaviour:

 sub render {
   my ( $self, $c, $vars ) = ( shift, @_ );
   my %extra_vars = (
     c    => $c,
     base => $c->req->base,
     name => $c->config->{'name'},
   );
   @$vars{ keys %extra_vars } = values %extra_vars;
   $self->next::method( @_ );
 }

In other words, overriding this method has much the same uses as overriding "template_vars" in Catalyst::View::TT. However, your method remains in the call stack during template execution, so you can do things like this:

 sub render {
   my ( $self, $c, $vars ) = ( shift, @_ );
   $vars{'helper_might_croak'} = sub { $c->app_method_that_might_croak( @_ ) };
   local $Carp::Internal{(__PACKAGE__)} = 1;
   $self->next::method( @_ );
 }

You could do this more conveniently with "expose_methods" in Catalyst::View::TT, but the %Carp::Internal line means exceptions caused by code in a template will be reported from the call site in the template, rather than from where the helper closure was defined in the view class. Catalyst::View::TT could be modified to offer this for helpers added through expose_methods, but your view class will not be able to do this for helpers added through an overridden template_vars method.

process_output

 $view->process_output( $c, $template_name, \%vars, \$output )
 $view->process_output( $c, $template_name, \%vars, \$output, ... )

This sets $c->response->body to $output and $c->response->content_type to $view->content_type (unless a content type has already been set), then it returns true.

You might want to override this method in your view class in order to send the output somewhere other than the HTTP response, e.g. e-mail.

process_error

 $view->process_error( $c, $template_name, \%vars )
 $view->process_error( $c, $template_name, \%vars, ... )

This logs $view->template->error at the error level and sets it as a $c->error, then it returns false.

You are unlikely to want to override this method in your view class except maybe in some kind of ancillary view (or something of the sort) where setting $c->error might be too drastic.

DIFFERENCES FROM Catalyst::View::TT

  • The biggest difference is probably that template_ext is appended to the template name in the "render" method rather than just one branch in "process", which means it is not only appended to the default template name derived from $c->action->reverse, but also to $c->stash->{'template'} and even to template names passed to "render" directly. Effectively you always use action paths to refer to templates rather than hardcoding your preferred extension for templates all over the code.

  • The "render" method works exactly like "process" in Template: it takes a scalar reference for storing the output and returns a boolean success value.

    This is very much unlike the original design of "render" in Catalyst::View::TT, which returns either the output or a Template Toolkit error object and leaves you to examine the value to figure out which case you are dealing with. This mistake was fixed by "render_die" in Catalyst::View::TT, at the the cost of guarding every render call with an eval.

  • There are no standard variables passed to templates by default. Most particularly there is no automatically passed c variable (nor any other name for the $c context object) because it only encourages unhealthy chumminess of the templates with the request object and especially the model. Catalyst is certainly not suited for the kind of quick and dirty small project where one might even conceivably get away with that sort of misbehaviour.

  • There is no equivalent to "expose_methods" in Catalyst::View::TT. This feature offers syntactic sugar for something already trivially simple but which should nevertheless not be done lightly.

  • There is no equivalent to "additional_template_paths" in Catalyst::View::TT. This seems too specialised a feature to support by default. If you need it you can implement it in your view class without too much code:

     use Template::Provider ();
     
     {
       my $dynamic_path = bless {}, do {
         package MyApp::Template::DynamicPath;
         sub paths { $_[0]{'paths'} || [] }
         __PACKAGE__
       };
     
       sub dynamic_path { $dynamic_path }
     }
     
     sub new_template {
       my ( $self, $c, $config ) = ( shift, @_ );
       $config->{'INCLUDE_PATH'} = Template::Provider->new( $config )->include_path;
       unshift @{ $config->{'INCLUDE_PATH'} }, $self->dynamic_path;
       $self->next::method( @_ );
     }
     
     sub render {
       my ( $self, $c ) = ( shift, @_ );
       local $self->dynamic_path->{'paths'} = $c->stash->{'additional_template_paths'};
       $self->next::method( @_ );
     }

    See "INCLUDE_PATH" in Template::Manual::Config for details.

SEE ALSO

Catalyst::Manual, Template::Manual

AUTHOR

Aristotle Pagaltzis <pagaltzis@gmx.de>

COPYRIGHT AND LICENSE

This software is copyright (c) 2020 by Aristotle Pagaltzis.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.