Split process and render in View::TT (patch from ash)
Matt S Trout [Sun, 23 Apr 2006 00:39:21 +0000 (00:39 +0000)]
MANIFEST
lib/Catalyst/View/TT.pm
t/07render.t [new file with mode: 0644]
t/lib/TestApp.pm
t/lib/TestApp/root/specified_template.tt [new file with mode: 0644]

index cf2f1e4..0ace99f 100644 (file)
--- a/MANIFEST
+++ b/MANIFEST
@@ -12,7 +12,9 @@ t/03podcoverage.t
 t/04pkgconfig.t
 t/05appconfig.t
 t/06includepath.t
+t/07render.t
 t/lib/TestApp.pm
+t/lib/TestApp/root/specified_template.tt
 t/lib/TestApp/root/test.tt
 t/lib/TestApp/root/test_include_path/testpath.tt
 t/lib/TestApp/View/TT/Appconfig.pm
index 159404a..ea16411 100644 (file)
@@ -192,6 +192,10 @@ checking and the chance of a memory leak:
 
     @{ $c->view('TT')->include_path } = qw/path another_path/;
 
+If you are calling C<render> directly then you can specify dynamic paths by 
+having a C<additional_include_paths> key with a value of additonal directories
+to search. See L<CAPTURING TEMPLATE OUTPUT> for an example showing this.
+
 =head2 RENDERING VIEWS
 
 The view plugin renders the template specified in the C<template>
@@ -204,13 +208,12 @@ item in the stash.
     }
 
 If a class item isn't defined, then it instead uses the
-current match, as returned by C<$c-E<gt>match>.  In the above 
+current match, as returned by C<< $c->match >>.  In the above 
 example, this would be C<message>.
 
 The items defined in the stash are passed to the Template Toolkit for
 use as template variables.
 
-sub message : Global {
     sub default : Private {
         my ( $self, $c ) = @_;
         $c->stash->{template} = 'message.tt2';
@@ -233,11 +236,35 @@ These can be accessed from the template in the usual way:
     The name is [% name %]
 
 
-The output generated by the template is stored in
-C<$c-E<gt>response-E<gt>output>.
+The output generated by the template is stored in C<< $c->response->body >>.
+
+=head2 CAPTURING TEMPLATE OUTPUT
+
+If you wish to use the output of a template for some other purpose than
+displaying in the response, e.g. for sending an email, this is possible using
+L<Catalyst::Plugin::Email> and the L<render> method:
+
+  sub send_email : Local {
+    my ($self, $c) = @_;
+    
+    $c->email(
+      header => [
+        To      => 'me@localhost',
+        Subject => 'A TT Email',
+      ],
+      body => $c->view('TT')->render($c, 'email.tt', {
+        additional_include_paths => [ $c->config->{root} . '/email_templates'],
+        email_tmpl_param1 => 'foo'
+        }
+      ),
+    );
+  # Redirect or display a message
+  }
 
 =head2 TEMPLATE PROFILING
 
+See L<C<TIMER>> property of the L<config> method.
+
 =head2 METHODS
 
 =over 4
@@ -269,10 +296,9 @@ sub new {
         %{ $class->config },
         %{$arguments},
     };
-    if ( ! (ref $config->{INCLUDE_PATH} eq 'ARRAY') ) {
+    if ( !( ref $config->{INCLUDE_PATH} eq 'ARRAY' ) ) {
         my $delim = $config->{DELIMITER};
-        my @include_path
-            = _coerce_paths( $config->{INCLUDE_PATH}, $delim );
+        my @include_path = _coerce_paths( $config->{INCLUDE_PATH}, $delim );
         if ( !@include_path ) {
             my $root = $c->config->{root};
             my $base = Path::Class::dir( $root, 'base' );
@@ -281,8 +307,6 @@ sub new {
         $config->{INCLUDE_PATH} = \@include_path;
     }
 
-
-
     # if we're debugging and/or the TIMER option is set, then we install
     # Template::Timer as a custom CONTEXT object, but only if we haven't
     # already got a custom CONTEXT defined
@@ -290,7 +314,7 @@ sub new {
     if ( $config->{TIMER} ) {
         if ( $config->{CONTEXT} ) {
             $c->log->error(
-                'Cannot use Template::Timer - a TT CONFIG is already defined'
+                'Cannot use Template::Timer - a TT CONTEXT is already defined'
             );
         }
         else {
@@ -304,24 +328,26 @@ sub new {
     }
     if ( $config->{PROVIDERS} ) {
         my @providers = ();
-        if ( ref($config->{PROVIDERS}) eq 'ARRAY') {
-            foreach my $p (@{$config->{PROVIDERS}}) {
+        if ( ref( $config->{PROVIDERS} ) eq 'ARRAY' ) {
+            foreach my $p ( @{ $config->{PROVIDERS} } ) {
                 my $pname = $p->{name};
                 eval "require Template::Provider::$pname";
-                if(!$@) {
-                    push @providers, "Template::Provider::${pname}"->new($p->{args});
+                if ( !$@ ) {
+                    push @providers,
+                      "Template::Provider::${pname}"->new( $p->{args} );
                 }
             }
         }
         delete $config->{PROVIDERS};
-        if(@providers) {
+        if (@providers) {
             $config->{LOAD_TEMPLATES} = \@providers;
         }
     }
 
     my $self = $class->NEXT::new(
         $c,
-        {   template => Template->new($config) || do {
+        {
+            template => Template->new($config) || do {
                 my $error = Template->error();
                 $c->log->error($error);
                 $c->error($error);
@@ -330,7 +356,7 @@ sub new {
             %{$config},
         },
     );
-    $self->include_path($config->{INCLUDE_PATH});
+    $self->include_path( $config->{INCLUDE_PATH} );
     $self->config($config);
 
     return $self;
@@ -338,15 +364,9 @@ sub new {
 
 =item process
 
-Renders the template specified in C<$c-E<gt>stash-E<gt>{template}> or
-C<$c-E<gt>action> (the private name of the matched action. 
-Template variables are set up from the contents of C<$c-E<gt>stash>, 
-augmented with C<base> set to C<$c-E<gt>req-E<gt>base>, C<c> to C<$c> and 
-C<name> to C<$c-E<gt>config-E<gt>{name}>. Alternately, the C<CATALYST_VAR>
-configuration item can be defined to specify the name of a template
-variable through which the context reference (C<$c>) can be accessed.
-In this case, the C<c>, C<base> and C<name> variables are omitted.
-Output is stored in C<$c-E<gt>response-E<gt>output>.
+Renders the template specified in C<< $c->stash->{template} >> or
+C<< $c->action >> (the private name of the matched action.  Calls L<render> to
+perform actual rendering. Output is stored in C<< $c->response->body >>.
 
 =cut
 
@@ -354,31 +374,21 @@ sub process {
     my ( $self, $c ) = @_;
 
     my $template = $c->stash->{template}
-      ||  $c->action . $self->config->{TEMPLATE_EXTENSION};
+      || $c->action . $self->config->{TEMPLATE_EXTENSION};
 
     unless ($template) {
         $c->log->debug('No template specified for rendering') if $c->debug;
         return 0;
     }
 
-    $c->log->debug(qq/Rendering template "$template"/) if $c->debug;
+    my $output = $self->render( $c, $template );
 
-    my $output;
-    my $vars = { $self->template_vars($c) };
-
-    unshift @{ $self->include_path },
-      @{ $c->stash->{additional_template_paths} }
-      if ref $c->stash->{additional_template_paths};
-    unless ( $self->template->process( $template, $vars, \$output ) ) {
-        my $error = $self->template->error;
-        $error = qq/Couldn't render template "$error"/;
+    if ( (Scalar::Util::blessed($output)||'') eq 'Template::Exception' ) {
+        my $error = qq/Coldn't render template "$output"/;
         $c->log->error($error);
         $c->error($error);
         return 0;
     }
-    splice @{ $self->include_path }, 0,
-      scalar @{ $c->stash->{additional_template_paths} }
-      if ref $c->stash->{additional_template_paths};
 
     unless ( $c->response->content_type ) {
         $c->response->content_type('text/html; charset=utf-8');
@@ -389,9 +399,51 @@ sub process {
     return 1;
 }
 
+=item render($c, $template, \%args)
+
+Renders the given template and returns output, or a L<Template::Exception>
+object upon error. 
+
+The template variables are set to C<%$args> if $args is a hashref, or 
+$C<< $c->stash >> otherwise. In either case the variables are augmented with 
+C<base> set to C< << $c->req->base >>, C<c> to C<$c> and C<name> to
+C<< $c->config->{name} >>. Alternately, the C<CATALYST_VAR> configuration item
+can be defined to specify the name of a template variable through which the
+context reference (C<$c>) can be accessed. In this case, the C<c>, C<base> and
+C<name> variables are omitted.
+
+C<$template> can be anything that Template::process understands how to 
+process, including the name of a template file or a reference to a test string.
+See L<Template::process|Template/process> for a full list of supported formats.
+
+=cut
+
+sub render {
+    my ( $self, $c, $template, $args ) = @_;
+
+    $c->log->debug(qq/Rendering template "$template"/) if $c->debug;
+
+    my $output;
+    my $vars = {
+        ( ref $args eq 'HASH' ? %$args : %{ $c->stash() } ),
+        $self->template_vars($c)
+    };
+
+    local $self->{include_path} = $self->include_path;
+    unshift @{ $self->{include_path} }, @{ $vars->{additional_template_paths} }
+      if ref $c->stash->{additional_template_paths};
+
+    unless ( $self->template->process( $template, $vars, \$output ) ) {
+        return $self->template->error;
+    }
+    else {
+        return $output;
+    }
+}
+
 =item template_vars
 
-Returns a list of keys/values to be used as the variables in the
+Returns a list of keys/values to be used as the catalyst variables in the
 template.
 
 =cut
@@ -407,10 +459,9 @@ sub template_vars {
         c    => $c,
         base => $c->req->base,
         name => $c->config->{name}
-      ),
-      %{ $c->stash() }
-
+      );
 }
+
 =item config
 
 This method allows your view subclass to pass additional settings to
diff --git a/t/07render.t b/t/07render.t
new file mode 100644 (file)
index 0000000..96b1e12
--- /dev/null
@@ -0,0 +1,17 @@
+use strict;
+use warnings;
+use Test::More tests => 5;
+
+use FindBin;
+use lib "$FindBin::Bin/lib";
+
+use_ok('Catalyst::Test', 'TestApp');
+
+my $response;
+ok(($response = request("/test_render?template=specified_template.tt&param=parameterized"))->is_success, 'request ok');
+is($response->content, "I should be a parameterized test in @{[TestApp->config->{name}]}", 'message ok');
+
+my $message = 'Dynamic message';
+
+ok(($response = request("/test_msg?msg=$message"))->is_success, 'request ok');
+is($response->content, "$message", 'message ok');
index 5220859..7a823ce 100755 (executable)
@@ -50,6 +50,22 @@ sub test_includepath : Local {
     }
 }
 
+sub test_render : Local {
+       my ($self, $c) = @_;
+
+       $c->stash->{message} = $c->view('TT')->render($c, $c->req->param('template'), {param => $c->req->param('param')});
+       $c->stash->{template} = 'test.tt';
+
+}
+
+sub test_msg : Local {
+       my ($self, $c) = @_;
+       my $tmpl = $c->req->param('msg');
+       
+       $c->stash->{message} = $c->view('TT')->render($c, \$tmpl);
+       $c->stash->{template} = 'test.tt';
+}
+
 sub end : Private {
     my ($self, $c) = @_;
 
diff --git a/t/lib/TestApp/root/specified_template.tt b/t/lib/TestApp/root/specified_template.tt
new file mode 100644 (file)
index 0000000..d4e252f
--- /dev/null
@@ -0,0 +1 @@
+I should be a [% param %] test in [% name %]