woops, backfix, re-added template_vars changes
[catagits/Catalyst-View-TT.git] / lib / Catalyst / View / TT.pm
index c66a8e2..74d538d 100644 (file)
@@ -1,14 +1,15 @@
 package Catalyst::View::TT;
 
 use strict;
-use base qw/Catalyst::Base/;
+use base qw/Catalyst::View/;
 use Template;
 use Template::Timer;
 use NEXT;
 
-our $VERSION = '0.13';
+our $VERSION = '0.20';
 
 __PACKAGE__->mk_accessors('template');
+__PACKAGE__->mk_accessors('include_path');
 
 =head1 NAME
 
@@ -16,71 +17,210 @@ Catalyst::View::TT - Template View Class
 
 =head1 SYNOPSIS
 
-    # use the helper
+# use the helper to create View
     myapp_create.pl view TT TT
 
-    # lib/MyApp/View/TT.pm
-    package MyApp::View::TT;
+# configure in lib/MyApp.pm
+
+    MyApp->config({
+        name     => 'MyApp',
+        root     => MyApp->path_to('root');,
+        'V::TT' => {
+            # any TT configurations items go here
+            INCLUDE_PATH => [
+              MyApp->path_to( 'root', 'src' ), 
+              MyApp->path_to( 'root', 'lib' ), 
+            ],
+            PRE_PROCESS        => 'config/main',
+            WRAPPER            => 'site/wrapper',
+            TEMPLATE_EXTENSION => '.tt',
+
+            # two optional config items
+            CATALYST_VAR => 'Catalyst',
+            TIMER        => 1,
+        },
+    });
+         
+# render view from lib/MyApp.pm or lib/MyApp::C::SomeController.pm
+    
+    sub message : Global {
+        my ( $self, $c ) = @_;
+        $c->stash->{template} = 'message.tt2';
+        $c->stash->{message}  = 'Hello World!';
+        $c->forward('MyApp::V::TT');
+    }
 
-    use base 'Catalyst::View::TT';
+# access variables from template
+
+    The message is: [% message %].
+    
+    # example when CATALYST_VAR is set to 'Catalyst'
+    Context is [% Catalyst %]          
+    The base is [% Catalyst.req.base %] 
+    The name is [% Catalyst.config.name %] 
+    
+    # example when CATALYST_VAR isn't set
+    Context is [% c %]
+    The base is [% base %]
+    The name is [% name %]
+
+=head1 DESCRIPTION
+
+This is the Catalyst view class for the L<Template Toolkit|Template>.
+Your application should defined a view class which is a subclass of
+this module.  The easiest way to achieve this is using the
+F<myapp_create.pl> script (where F<myapp> should be replaced with
+whatever your application is called).  This script is created as part
+of the Catalyst setup.
+
+    $ script/myapp_create.pl view TT TT
+
+This creates a MyApp::V::TT.pm module in the F<lib> directory (again,
+replacing C<MyApp> with the name of your application) which looks
+something like this:
+
+    package FooBar::V::TT;
+    
+    use strict;
+     use base 'Catalyst::View::TT';
 
     __PACKAGE__->config->{DEBUG} = 'all';
 
-    # in practice you'd probably set this from a config file;
-    # defaults to $c->config->root
-    __PACKAGE__->config->{INCLUDE_PATH} =
-       '/usr/local/generic/templates:/usr/local/myapp/templates';
+Now you can modify your action handlers in the main application and/or
+controllers to forward to your view class.  You might choose to do this
+in the end() method, for example, to automatically forward all actions
+to the TT view class.
 
-    1;
+    # In MyApp or MyApp::Controller::SomeController
     
-    # Meanwhile, maybe in a private C<end> action
-    $c->forward('MyApp::View::TT');
+    sub end : Private {
+        my( $self, $c ) = @_;
+        $c->forward('MyApp::V::TT');
+    }
 
+=head2 CONFIGURATION
 
-=head1 DESCRIPTION
+There are a three different ways to configure your view class.  The
+first way is to call the C<config()> method in the view subclass.  This
+happens when the module is first loaded.
 
-This is the Catalyst view class for the L<Template
-Toolkit|Template>. Your application subclass should inherit from this
-class. This plugin renders the template specified in
-C<$c-E<gt>stash-E<gt>{template}>, or failing that,
-C<$c-E<gt>request-E<gt>match>. The template variables are set up from
-the contents of C<$c-E<gt>stash>, augmented with template variable
-C<base> set to Catalyst's C<$c-E<gt>req-E<gt>base>, template variable
-C<c> to Catalyst's C<$c>, and template variable C<name> to Catalyst's
-C<$c-E<gt>config-E<gt>{name}>. The output is stored in
-C<$c-E<gt>response-E<gt>output>.
+    package MyApp::V::TT;
+    
+    use strict;
+    use base 'Catalyst::View::TT';
 
-If you want to override TT config settings, you can do it in your
-application's view class by setting
-C<__PACKAGE__-E<gt>config-E<gt>{OPTION}>, as shown in the Synopsis. Of
-interest might be C<EVAL_PERL>, which is disabled by default,
-C<INCLUDE_PATH>, and C<LOAD_TEMPLATES>, which is set to use the
-provider.
+    MyApp::V::TT->config({
+        INCLUDE_PATH => [
+            MyApp->path_to( 'root', 'templates', 'lib' ),
+            MyApp->path_to( 'root', 'templates', 'src' ),
+        ],
+        PRE_PROCESS  => 'config/main',
+        WRAPPER      => 'site/wrapper',
+    });
+
+The second way is to define a C<new()> method in your view subclass.
+This performs the configuration when the view object is created,
+shortly after being loaded.  Remember to delegate to the base class
+C<new()> method (via C<$self-E<gt>NEXT::new()> in the example below) after
+performing any configuration.
+
+    sub new {
+        my $self = shift;
+        $self->config({
+            INCLUDE_PATH => [
+                MyApp->path_to( 'root', 'templates', 'lib' ),
+                MyApp->path_to( 'root', 'templates', 'src' ),
+            ],
+            PRE_PROCESS  => 'config/main',
+            WRAPPER      => 'site/wrapper',
+        });
+        return $self->NEXT::new(@_);
+    }
+The final, and perhaps most direct way, is to define a class
+item in your main application configuration, again by calling the
+uniquitous C<config()> method.  The items in the class hash are
+added to those already defined by the above two methods.  This happens
+in the base class new() method (which is one reason why you must
+remember to call it via C<NEXT> if you redefine the C<new()> method in a
+subclass).
+
+    package MyApp;
+    
+    use strict;
+    use Catalyst;
+    
+    MyApp->config({
+        name     => 'MyApp',
+        root     => MyApp->path_to('root'),
+        'V::TT' => {
+            INCLUDE_PATH => [
+                MyApp->path_to( 'root', 'templates', 'lib' ),
+                MyApp->path_to( 'root', 'templates', 'src' ),
+            ],
+            PRE_PROCESS  => 'config/main',
+            WRAPPER      => 'site/wrapper',
+        },
+    });
+
+Note that any configuration items defined by one of the earlier
+methods will be overwritten by items of the same name provided by the
+latter methods.  
+
+=head2 DYNAMIC INCLUDE_PATH
+
+It is sometimes needed to dynamically add additional paths to the 
+INCLUDE_PATH variable of the template object. This can be done by setting
+'additional_include_paths' on stash to a referrence to an array with 
+additional paths:
+
+    $c->stash->{additional_template_paths} = [$c->config->{root} . '/test_include_path']; 
+
+=head2 RENDERING VIEWS
+
+The view plugin renders the template specified in the C<template>
+item in the stash.  
+
+    sub message : Global {
+        my ( $self, $c ) = @_;
+        $c->stash->{template} = 'message.tt2';
+        $c->forward('MyApp::V::TT');
+    }
 
-If you want to use C<EVAL_PERL>, add something like this:
+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 
+example, this would be C<message>.
 
-    __PACKAGE__->config->{EVAL_PERL} = 1;
-    __PACKAGE__->config->{LOAD_TEMPLATES} = undef;
+The items defined in the stash are passed to the Template Toolkit for
+use as template variables.
 
-If you have configured Catalyst for debug output, C<Catalyst::View::TT>
-will enable profiling of template processing (using
-L<Template::Timer>). This will embed HTML comments in the output from
-your templates, such as:
+sub message : Global {
+    sub default : Private {
+        my ( $self, $c ) = @_;
+        $c->stash->{template} = 'message.tt2';
+        $c->stash->{message}  = 'Hello World!';
+        $c->forward('MyApp::V::TT');
+    }
 
-    <!-- TIMER START: process mainmenu/mainmenu.ttml -->
-    <!-- TIMER START: include mainmenu/cssindex.tt -->
-    <!-- TIMER START: process mainmenu/cssindex.tt -->
-    <!-- TIMER END: process mainmenu/cssindex.tt (0.017279 seconds) -->
-    <!-- TIMER END: include mainmenu/cssindex.tt (0.017401 seconds) -->
+A number of other template variables are also added:
 
-    ....
+    c      A reference to the context object, $c
+    base   The URL base, from $c->req->base()
+    name   The application name, from $c->config->{ name }
 
-    <!-- TIMER END: process mainmenu/footer.tt (0.003016 seconds) -->
+These can be accessed from the template in the usual way:
+
+<message.tt2>:
 
-You can suppress template profiling when debug is enabled by setting:
+    The message is: [% message %]
+    The base is [% base %]
+    The name is [% name %]
 
-    __PACKAGE__->config->{CONTEXT} = undef;
 
+The output generated by the template is stored in
+C<$c-E<gt>response-E<gt>output>.
+
+=head2 TEMPLATE PROFILING
 
 =head2 METHODS
 
@@ -93,23 +233,91 @@ and reads the application config.
 
 =cut
 
+sub _coerce_paths {
+    my ( $paths, $dlim ) = shift;
+    return () if ( !$paths );
+    return @{$paths} if ( ref $paths eq 'ARRAY' );
+
+    # tweak delim to ignore C:/
+    unless ( defined $dlim ) {
+        $dlim = ( $^O eq 'MSWin32' ) ? ':(?!\\/)' : ':';
+    }
+    return split( /$dlim/, $paths );
+}
+
 sub new {
     my ( $class, $c, $arguments ) = @_;
+    my $config = {
+        EVAL_PERL          => 0,
+        TEMPLATE_EXTENSION => '',
+        %{ $class->config },
+        %{$arguments},
+    };
+    if ( ! (ref $config->{INCLUDE_PATH} eq 'ARRAY') ) {
+        my $delim = $config->{DELIMITER};
+        my @include_path
+            = _coerce_paths( $config->{INCLUDE_PATH}, $delim );
+        if ( !@include_path ) {
+            my $root = $c->config->{root};
+            my $base = Path::Class::dir( $root, 'base' );
+            @include_path = ( "$root", "$base" );
+        }
+        $config->{INCLUDE_PATH} = \@include_path;
+    }
 
-    my $root = $c->config->{root};
 
-    my %config = (
-        EVAL_PERL    => 0,
-        INCLUDE_PATH => [ $root, "$root/base" ],
-        %{ $class->config },
-        %{ $arguments }
-    );
 
-    if ( $c->debug && not exists $config{CONTEXT} ) {
-       $config{CONTEXT} = Template::Timer->new(%config);
+    # 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
+
+    if ( $config->{TIMER} ) {
+        if ( $config->{CONTEXT} ) {
+            $c->log->error(
+                'Cannot use Template::Timer - a TT CONFIG is already defined'
+            );
+        }
+        else {
+            $config->{CONTEXT} = Template::Timer->new(%$config);
+        }
+    }
+
+    if ( $c->debug && $config->{DUMP_CONFIG} ) {
+        use Data::Dumper;
+        $c->log->debug( "TT Config: ", Dumper($config) );
+    }
+    if ( $config->{PROVIDERS} ) {
+        my @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});
+                }
+            }
+        }
+        delete $config->{PROVIDERS};
+        if(@providers) {
+            $config->{LOAD_TEMPLATES} = \@providers;
+        }
     }
 
-    return $class->NEXT::new( $c, { template => Template->new( \%config ) } );
+    my $self = $class->NEXT::new(
+        $c,
+        {   template => Template->new($config) || do {
+                my $error = Template->error();
+                $c->log->error($error);
+                $c->error($error);
+                return undef;
+            },
+            %{$config},
+        },
+    );
+    $self->include_path($config->{INCLUDE_PATH});
+    $self->config($config);
+
+    return $self;
 }
 
 =item process
@@ -118,15 +326,20 @@ Renders the template specified in C<$c-E<gt>stash-E<gt>{template}> or
 C<$c-E<gt>request-E<gt>match>. 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}>. Output is stored in
-C<$c-E<gt>response-E<gt>output>.
+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>.
 
 =cut
 
 sub process {
     my ( $self, $c ) = @_;
 
-    my $template = $c->stash->{template} || $c->request->match;
+    my $template = $c->stash->{template}
+      || ( $c->request->match || $c->request->action )
+      . $self->config->{TEMPLATE_EXTENSION};
 
     unless ($template) {
         $c->log->debug('No template specified for rendering') if $c->debug;
@@ -134,29 +347,24 @@ sub process {
     }
 
     $c->log->debug(qq/Rendering template "$template"/) if $c->debug;
-    
+
     my $output;
+    my $vars = { $self->template_vars($c) };
 
-    unless (
-        $self->template->process(
-            $template,
-            {
-                base => $c->req->base,
-                c    => $c,
-                name => $c->config->{name},
-                %{ $c->stash }
-            },
-            \$output
-        )
-      )
-    {
+    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"/;
         $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');
     }
@@ -166,23 +374,120 @@ sub process {
     return 1;
 }
 
+=item template_vars
+
+Returns a list of keys/values to be used as the variables in the
+template.
+
+=cut
+
+sub template_vars {
+    my ( $self, $c ) = @_;
+
+    my $cvar = $self->config->{CATALYST_VAR};
+
+    defined $cvar
+      ? ( $cvar => $c )
+      : (
+        c    => $c,
+        base => $c->req->base,
+        name => $c->config->{name}
+      ),
+      %{ $c->stash() }
+
+}
 =item config
 
-This allows your view subclass to pass additional settings to the
-TT config hash.
+This method allows your view subclass to pass additional settings to
+the TT configuration hash, or to set the options as below:
+
+=over 2
+
+=item C<CATALYST_VAR> 
+
+Allows you to change the name of the Catalyst context object. If set, it will also
+remove the base and name aliases, so you will have access them through <context>.
+
+For example:
+
+    MyApp->config({
+        name     => 'MyApp',
+        root     => MyApp->path_to('root'),
+        'V::TT' => {
+            CATALYST_VAR => 'Catalyst',
+        },
+    });
+
+F<message.tt2>:
+
+    The base is [% Catalyst.req.base %]
+    The name is [% Catalyst.config.name %]
+
+=item C<TIMER>
+
+If you have configured Catalyst for debug output, and turned on the TIMER setting,
+C<Catalyst::View::TT> will enable profiling of template processing
+(using L<Template::Timer>). This will embed HTML comments in the
+output from your templates, such as:
+
+    <!-- TIMER START: process mainmenu/mainmenu.ttml -->
+    <!-- TIMER START: include mainmenu/cssindex.tt -->
+    <!-- TIMER START: process mainmenu/cssindex.tt -->
+    <!-- TIMER END: process mainmenu/cssindex.tt (0.017279 seconds) -->
+    <!-- TIMER END: include mainmenu/cssindex.tt (0.017401 seconds) -->
+
+    ....
+
+    <!-- TIMER END: process mainmenu/footer.tt (0.003016 seconds) -->
+
+
+=item C<TEMPLATE_EXTENSION>
+
+a sufix to add when looking for templates bases on the C<match> method in L<Catalyst::Request>.
+
+For example:
+
+  package MyApp::C::Test;
+  sub test : Local { .. } 
+
+Would by default look for a template in <root>/test/test. If you set TEMPLATE_EXTENSION to '.tt', it will look for
+<root>/test/test.tt.
+
+=back
 
 =back
 
+=head2 HELPERS
+
+The L<Catalyst::Helper::View::TT> and
+L<Catalyst::Helper::View::TTSite> helper modules are provided to create
+your view module.  There are invoked by the F<myapp_create.pl> script:
+
+    $ script/myapp_create.pl view TT TT
+
+    $ script/myapp_create.pl view TT TTSite
+
+The L<Catalyst::Helper::View::TT> module creates a basic TT view
+module.  The L<Catalyst::Helper::View::TTSite> module goes a little
+further.  It also creates a default set of templates to get you
+started.  It also configures the view module to locate the templates
+automatically.
+
 =head1 SEE ALSO
 
-L<Catalyst>, L<Template::Manual>
+L<Catalyst>, L<Catalyst::Helper::View::TT>,
+L<Catalyst::Helper::View::TTSite>, L<Template::Manual>
 
-=head1 AUTHOR
+=head1 AUTHORS
 
 Sebastian Riedel, C<sri@cpan.org>
+
 Marcus Ramberg, C<mramberg@cpan.org>
+
 Jesse Sheidlower, C<jester@panix.com>
 
+Andy Wardley, C<abw@cpan.org>
+
 =head1 COPYRIGHT
 
 This program is free software, you can redistribute it and/or modify it